引题
输入一个数N(10进制),输出N的二进制中“1”个数
这是一道谷歌、微软等大厂的一道经典面试题(原题给的二进制N)
输入格式:
7
输出格式:
3
解析:
7(10)==>0111(2)
含有三个1,故输出3;
虽然在cpp中我们有__builtin_popcount(n)函数,可以很快的帮助我们计算出这个结果,但是我们学习的思想应该是剖析这个问题。工具随便用,但我们也要想想没有工具的时候我们应该怎么办。
分析
解法一:我曾经的一位伟大的数学老师告诉我:“数学的最高境界就是一个一个数”,但问题是该如何在计算机中实现呢?
其实计算机执行的是数学指令,其实这也是再问,在数学上如何实现?我们引进两个位操作符:
按位与(&)和shift right(>>)
相信各位在《数字电路》和《计算机组成原理》中都见过这两种运算符物理方面的硬件操作,当然本次我们不讲这两本书。
按位与(&)
应用范围:整数范围且二进制;
运算:同位当中,均为1结果为1,否则为0;
例如:
3==>0011
5==>0101
3&5==>0001
shift right(>>)
应用范围:必须在整数范围内进行;
运算:把二进制数向右移动一位,右边丢弃,左边补0;
如果给定一个x(一位),做x&1的运算,那么其运算的结果就是在数x中“1“的个数,再结合我们的shift right操作,我们就能从右向左,对每一位进行计算来去数“1”的个数
我们实际讲解一下
第一次cnt为0;cnt+=0;
x | 1 | 1 | 1 | 0 |
& | 0 | 0 | 0 | 1 |
运算结果 | 0 | 0 | 0 | 0 |
shift right操作;
第二次cnt为0;cnt+=1;
x | 0 | 1 | 1 | 1 |
& | 0 | 0 | 0 | 1 |
运算结果 | 0 | 0 | 0 | 1 |
shift right操作;
第三次cnt值为1;cnt+=1;cnt值为2
x | 0 | 0 | 1 | 1 |
& | 0 | 0 | 0 | 1 |
运算结果 | 0 | 0 | 0 | 1 |
shift right操作;
第四次cnt值为2;cnt+=1;cnt值为3
x | 0 | 0 | 0 | 1 | |
& | 0 | 0 | 0 | 1 | |
运算结果 | 0 | 0 | 0 | 1 |
这样我们就得到的最后的结果为3;
你也就成功入门了,当然这还是拿不到offer的。我们发现解法一中,对每一位都进行了运算,这大大增加了时间的成本,所以我们可以优化做到只数1,0直接跳过
解法二:给定一个x,让x进行x&(x-1)操作,然后x&(x-1)再与x&(x-1)-1进行按位与操作,直到最后的结果为0,那么执行的次数就是x中含有1的个数
举个例子:
第一次
X | 1 | 0 | 0 | 0 | 1 |
x-1 | 1 | 0 | 0 | 0 | 0 |
运算结果 | 1 | 0 | 0 | 0 | 0 |
第二次
X | 1 | 0 | 0 | 0 | 0 |
x-1 | 0 | 1 | 1 | 1 | 1 |
运算结果 | 0 | 0 | 0 | 0 | 0 |
共执行两次,所以“1”的个数为2;
我们不难看出,每次进行-1的操作,无非就是消除数据中最后一位的1,直到1消除完毕。
但是,这还是用到了循环,需要消耗的时间无法确定。
所以我们还有接下来的方法:
解法三:
a |
b0 = ( a>>0 ) & 01 01 01 01 01 01 01 01 |
b1 = ( a>>1 ) & 01 01 01 01 01 01 01 01 |
c = b0 + b1 |
d0 = ( c>>0 ) & 0011 0011 0011 0011 |
d2 = ( c>>2 ) & 0011 0011 0011 0011 |
e = d0 + d2 |
f0 = ( e>>0 ) & 00001111 00001111 |
f4 = ( e>>4 ) & 00001111 00001111 |
g = f0 + f4 |
h0 = ( g>>0 ) & 0000000011111111 |
h5 = ( g>>8 ) & 0000000011111111 |
i = h0 + h8 |
这是一个16位数据的计算过程,无论你数据中含有多少个1,它都是固定的计算次数,时间复杂度也是O(logN),这个算法中涉及到了归并的思想
我们举个例子
x | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 1 |
相邻两位里1的个数 | 1 | 2 | 0 | 1 | ||||
相邻四位里1的个数 | 3 | 1 | ||||||
相邻八位里1的个数 | 4 |
&01===>处理相邻的两位
&0011==>处理相邻的四位
&00001111===>处理相邻的八位
看到这里,你就离你的offer更近了,但还不行。在实际工作中,我们要有应对工作的方法。
方案四:空间换时间
我们可以使用最朴素的方式,把所需要范围内的所有数据都进行计算,把它储存在一张表中,需要的时候拿出来查表就行了。
解题
最后在让我们回归那道题:
讲了这么多,我们也需要程序上的操作;
这里我们使用解法二
int GetOneCount(int x){
int count = 0;
if(x == 0){
return 0;
}
while(x != 0){
count++;
x = x & (x - 1);
}
return count;
}