起因是在leetcode上写到了一个计算Hamming Distance(汉明距离)的题目。
The Hamming distance between two integers is the number of positions at which the corresponding bits are different.
Given two integers x and y, calculate the Hamming distance.
开始我的解法为遍历,按位计算。
public int hammingDistance(int x, int y) {
int count = 0, num = x ^ y;
for (int i = 0; i < 32; i++) {
if ((num >>> i & 1) == 1) // can be replaced => count += (num >>> i & 1);
count++;
}
return count;
}
写完后觉得时间复杂度应该是可以优化的,就想起了Integer#bitCount(int)
方法,该方法返回数字二进制中1的数量。JDK的实现都是比较高效的,所以习惯性看了眼源码。
/**
* Returns the number of one-bits in the two's complement binary
* representation of the specified {@code int} value. This function is
* sometimes referred to as the <i>population count</i>.
*
* @param i the value whose bits are to be counted
* @return the number of one-bits in the two's complement binary
* representation of the specified {@code int} value.
* @since 1.5
*/
public static int bitCount(int i) {
// HD, Figure 5-2
i = i - ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i + (i >>> 4)) & 0x0f0f0f0f;
i = i + (i >>> 8);
i = i + (i >>> 16);
return i & 0x3f;
}
这种充斥着位运算的算法第一眼看上去往往都是比较不容易理解的,需要仔细分析下原理。
算法思想为分而治之,先计算每2个bit位中有几个1,结果保存至这2个bit位上;再计算每4个bit位上有几个1,保存至这4个bit位上…
直到最后一步合并低16bit和高16bit上结果即可,即通过五次&(与)
以及>>>(无符号右移)
来实现。
Hexadecimal | Binary |
---|---|
0x55555555 | 0b01010101010101010101010101010101 |
0x33333333 | 0b00110011001100110011001100110011 |
0x0f0f0f0f | 0b00001111000011110000111100001111 |
0x00ff00ff | 0b00000000111111110000000011111111 |
0x0000ffff | 0b00000000000000001111111111111111 |
0x3f | 0b00111111 |
(i & 0x55555555) + ((i >>> 1) & 0x55555555)
即为第一步,先计算每两个bit位中有几个1,结果保存至这两个bit位上。i & 0x55555555
计算了每两位中右1位中1的数量;((i >>> 1) & 0x55555555)
将i无符号右移1位后做相同与运算,也就是计算了每两位中左1位中1的数量。
有此可得算法原型。
public static int bitCount(int i) {
i = (i & 0x55555555) + ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i & 0x0f0f0f0f) + ((i >>> 4) & 0x0f0f0f0f);
i = (i & 0x00ff00ff) + ((i >>> 8) & 0x00ff00ff);
i = (i & 0x0000ffff) + ((i >>> 16) & 0x0000ffff);
return i;
}
稍加优化后即为Integer#bitCount(int)
的实现,优化的部分为减少与运算,将消位操作放到最后。
还有种比较有趣的BitCount算法为HAKMEM/MIT HAKMEM算法,感兴趣可以了解下。