本文题目难度标识:🟩简单,🟨中等,🟥困难。

汉明重量

汉明重量 是一串符号中非零符号的个数。对于二进制串来说就是 1 的个数 [1]

编写一个函数,获取一个正整数的二进制形式并返回其二进制表达式中设置位的个数。

常规算法

常规方法:循环检查二进制位,判断每一位整数是否为 1。

  • 时间复杂度:O(k)O(k),k 是二进制整数的位数
  • 空间复杂度:O(1)O(1)

Brian Kernighan 算法

Brian Kernighan 算法发布在 1988 年出版的 The C Programming Language (Second Edition) (由 Brian W. Kernighan 和 Dennis M. Ritchie 编写)的练习中,但是 Donald Knuth 在 2006 年 4 月 19 日指出,该方法第一次是由 Peter Wegner 在 1960 年的 CACM3 上出版。读者可以在上述书籍中找到更多位操作的技巧。

「Brian Kernighan 算法」用于清除二进制串中最右边的 1。

统计数 x 二进制表示中 1 的个数可以使用 Brian Kernighan 算法可以加速统计。

算法核心:记 f(x)f(x) 表示 xx−1 进行与运算所得的结果,即 f(x)=f(x)=x & (x−1),那么 f(x)f(x) 恰为 x 删去其二进制表示中最右侧的 1 的结果。

例子

x = 0b10001000
x-1 = 0b10000111
x&(x-1) = 0b10000000

时间复杂度:O(logC)O(\log C)。C 表示元素的数据范围,循环次数等于 x 的二进制位中 1 的个数。当 x 为 32 位整数时 ,最坏情况下 x 二进制位全部为 1,我们需要循环 log231=31\log 2^{31}=31 次。

空间复杂度:O(1)O(1),我们只需要常数的空间保存若干变量。

相关题目:🟨 201. 数字范围按位与 - 力扣(LeetCode)

MIT HAKM 算法

MIT Hackmem 169

求 32 位无符号数的二进制形式中 1 的个数。

核心原理是分治法。

1
2
3
4
5
6
7
8
int bitcount(unsigned int n)
{
unsigned int tmp;

tmp = n - ((n >> 1) & 033333333333)
- ((n >> 2) & 011111111111); // 注意这是8进制表示
return ((tmp + (tmp >> 3)) & 030707070707)%63;
}

image.png

考虑 3 位 2 进制数 x:abc

  • x>>1 == 4a+2b+c>>1==2a+b
  • x>>2 == 4a+2b+c>>2==a
  • 4a+2b+c-(2a+b)-a == a+b+c
  • 因此 x-x>>1-x>>2 == a+b+c 即为 1 的个数

把 32 位数按每 3 位划分,通过掩码,完成每 3 位的移位减操作,这样每 3 位中的值就是这 3 位中 1 的个数。

每相邻 3 位相加,mask 后,每 6 位中的值即为 6 位中 1 的个数

64^5*t6+64^4*t5+64^3*t4+64^2*t3+64*t2+t1 mod 63 = t6+t5+t4+t3+t2+t1 即为 32 位中 1 的个数。取模 63 不出错的要点在于 x 是个 32 位的数,t6+t5+t4+t3+t2+t1 不会被模掉。

注意是取模 63,而不是 64。

汉明距离

本小节为汉明重量的延伸应用。

给你两个整数 x 和 y,计算并返回它们之间的汉明距离。

两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。汉明距离 广泛应用于多个领域。在编码理论中用于错误检测,在信息论中量化字符串之间的差异。

方法:

  1. 计算 x 和 y 之间的汉明距离,可以先计算 x^y
  2. 然后统计结果中等于 1 的位数。可以使用 Brian Kernighan 算法。

其他相关题目:

本文参考

  • 《深入理解计算机系统》第 3 版
  • 研究生课程 CSAPP 课件
  • LeetCode 相关题目

  1. 2023 USTC SSE 复试机试题 ↩︎