【4.15】求二进制中1的个数。例如,10=1010,1的个数为2。【每日一题】
此题主要考查位运算,当然不会用位运算的人可能会这么写:
1.可能是最笨的方法了
int numOfOne(unsigned num) {
int count = 0;
while (num) {
count += num % 2;
num /= 2;
}
return count;
}
使用位运算的原因是位运算比乘法要快,这是与计算机的硬件实现相关的。下面是使用位运算中的一种比较弱多解法,好处是逻辑清楚,易懂。
2.用位运算来代替方法1中的除法和取模运算
int numOfOne(unsigned num) {
int count = 0;
while (num) {
count += num & 1;
num >>= 1;
}
return count;
}
方法2中的方法已经比方法1中的方法好很多了,但是有没有更好的方法呢?下面是编程之美中给出的方法
3.只用一次位运算
int hammingWeight(uint32_t n) {
int count = 0;
while (n) {
n = n & n - 1;
++count;
}
return count;
}
这个方法中,每次循环仅用了一次位运算,从这里已经比方法2省了一次位运算,另外,我们可以发现如下规律:
- 当n不为0时,n-1总是从把n中最后一个1(二进制位)改为0(二进制位),同时将其后的所有位置0,而与此同时对于前面的所有位,n与n-1每一位均相同,因此&操作并不会改变前面所有位数的值。这样n & (n - 1)的最终效果是把n的最后一个1置0,因此我们给count加1
- 在每次循环时,总是找到n的最后一个1去操作,因此跳过了n所有二进制位中后面的连续的多个0位,可以有效减少循环执行次数
如前所述,方法3中循环可以跳过二进制位中的0位以减少循环执行次数,我们举例如下:
【例】
对于十进制数100,其二进制表示位1100100
- 对于方法2,循环将执行7次
次数 | 执行前num值 | 执行后num值 | 执行后count值 |
---|---|---|---|
1 | 1100100 | 110010 | 0 |
2 | 110010 | 11001 | 0 |
3 | 11001 | 1100 | 1 |
4 | 1100 | 110 | 1 |
5 | 110 | 11 | 1 |
6 | 11 | 1 | 2 |
7 | 1 | 0 | 3 |
- 对于方法3,循环执行3次
次数 | 执行前n值 | 执行后n值 | 执行后count值 |
---|---|---|---|
1 | 1100100 | 1100000 | 1 |
2 | 1100000 | 1000000 | 2 |
3 | 1000000 | 0 | 3 |
从例子中可以看出,方法3可以显著减少循环的执行次数。