题目描述

✅ 剑指 Offer 15. 二进制中1的个数

image-20261001230752539

image-20260928203903818

image-20260928203903819

题意分析

统计输入的 32 位二进制表示中有多少个 1。Java 用有符号 int 保存这 32 位,Go 使用 uint32;最高位是否被解释为符号位,不影响它是否应被计数。

解法:Brian Kernighan 位运算

核心思路

[!blue]

对非零的 n,找到它最低的一位 1,这一位右侧必然全是 0。执行 n - 1 时,需要从这个 1 借位:它变成 0,右侧所有 0 变成 1,更高位保持不变。

再计算 n & (n - 1),更高位原样保留,最低的 1 因为与 0 相与而被清除,右侧各位则因为原先是 0 仍然为 0。因此,这一步恰好删掉一个 1,不改变其余的 1。

每清除一次就将 count 加一,始终保持“已清除的个数加上剩余 1 的个数”等于原答案。位串归零时已经没有遗漏,count 就是结果;每轮至少减少一个 1,最多执行 32 次。

Java 的减法和按位与都作用于固定的 32 位补码,即使输入为负数,这个清除规则仍成立。因此循环条件必须是 n != 0,不能要求 n > 0。输入为 0 时不进入循环,直接得到 0。

解题步骤

  • 计数从零开始。
  • 位串非零时清除最低的一并计数。
  • 位串归零后返回。

代码实现

public class Solution {
    public int hammingWeight(int n) {
        int count = 0;

        // 按位串判非零,不能用正负判断是否仍有一
        while (n != 0) {
            // 每轮恰好清除最低的一,计数加一即可
            n &= n - 1;
            count++;
        }

        return count;
    }
}
func hammingWeight(num uint32) int {
    count := 0
    // 按位串判非零,不能用正负判断是否仍有一
    for num != 0 {
        // 每轮恰好清除最低的一,计数加一即可
        num &= num - 1
        count++
    }
    return count
}

复杂度分析

  • 时间复杂度:$O(b+1)$,其中 $b$ 为 1 的数量,循环恰好执行 $b$ 次;固定 32 位输入下最坏为 $O(1)$。
  • 空间复杂度:$O(1)$,一个计数器。

关键点总结

[!green]

  • 处理的是位串,符号解释不改变应统计的位。
  • 每轮清除一个一,不是简单右移一位。

易错点总结

[!yellow]

  • 循环条件大于零会漏掉 Java 最高位为一的输入。
  • 先取绝对值改变原位串,统计对象已不同。
  • 把最低一的位置当作本次应增加的数量,会多算。

相似题目

题目 难度 关联与区别
338. 比特位计数 简单 对0到n批量求位数时,可利用更小数的计数结果复用状态,而不是逐个重新统计。
461. 汉明距离 简单 先异或得到不同位,再调用位计数即得到汉明距离。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2022/18558581
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!