一、算法背景

Brian Kernighan 算法是一种高效计算二进制数中 1 的个数(即汉明权重,Hamming Weight)的技巧。相比于逐位检查的常规方法,它只遍历值为 1 的位,而不是所有位,因此在二进制中 1 较少时尤为高效。


二、核心原理

算法的关键在于一个非常巧妙的位运算恒等式:n & (n - 1) 的结果是将 n 的二进制表示中最低位的 1 变为 0。

为什么?

  • 对于任意非零整数 nn - 1 会:

    • 将最右侧的 1 变为 0

    • 将该位右边的所有 0 变为 1

    • 左侧高位保持不变

例如:n = 10100(20)

text

n       = 10100
n - 1   = 10011
n & (n-1) = 10000  ← 最低位的 1 被清除了

因此,每次执行 n &= n - 1,就能消除一个 1,循环次数正好等于 1 的个数。


三、代码实现

int countBits(int n) {
    int count = 0;
    while (n != 0) {
        n &= (n - 1);
        count++;
    }
    return count;
}

四、复杂度分析

维度 复杂度 说明
时间复杂度 O(k) k 为二进制中 1 的个数,远优于固定循环 32/64 次
空间复杂度 O(1) 只使用常数个变量

五、边界情况与注意事项

1. 负数的处理

在 Java 中,有符号整数使用补码表示。Kernighan 算法使用 n & (n - 1) 操作,对负数同样有效,因为:

  • n - 1 在补码下仍然会将最低位的 1 变为 0,该位右侧的 0 变为 1

  • & 操作后,该最低位的 1 被清零,高位保持不变

  • 循环 while (n != 0) 会在所有 1 被清除后结束(包括符号位)

  • // Java 中直接使用 int(包括负数)完全没问题
    public static int countBits(int n) {
        int count = 0;
        while (n != 0) {
            n &= (n - 1);
            count++;
        }
        return count;
    }
    
    System.out.println(countBits(-1));   // 补码全1 → 32
    System.out.println(countBits(-13));  // 补码中1的个数 → 30

2. n = 0 的情况

循环条件 while (n != 0) 为 false,直接返回 0,结果正确。

System.out.println(countBits(0));   // 输出 0

六、相关位运算技巧

表达式 效果 常见用途
n & -n 提取最低位的 1(lowbit) 树状数组(Fenwick Tree)
n ^ (n - 1) 生成从最低位到第一个 1 的掩码 位运算扩展
n | (n - 1) 将最低位的 1 之后所有位变为 1 位域合并

七、示例演示

以 n = 13(二进制 1101)为例:

步骤 当前 n n-1 n & (n-1) 消除的 1 计数
初始 1101 0
1101 1100 1100 最低位 1
1100 1011 1000 第二位 2
1000 0111 0000 最高位 3

最终 count = 3,即 1101 中有 3 个 1。


八、其他统计方法对比

方法 循环次数 优点 缺点
逐位检查 固定 32/64 次 简单直观 即使 1 很少也要循环所有位
Kernighan 等于 1 的个数 高效,位数越稀疏越快 仍然依赖循环次数
查表法(分块) 固定次数(如 4 次查表) 速度极快 需要额外内存
内置指令(__builtin_popcount O(1) 硬件级 最快 非跨平台
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐