今天遇到一个面试题:
如何统计一个二进制整数num中1的个数.这里参考了
https://blog.csdn.net/peiyao456/article/details/51724099
的第4种思路,非常巧妙,这里写一下心得笔记
我们以8位整数为例,
首先输入num可以看成一个二进制序列
num= a1 b1 a2 b2 a3 b3 a4 b4,
可以认为他们自动分成8组,每组长度为1,即
num = {a1},{b1},{a2},{b2},{a3},{b3},{a4},{b4},
每组中的值正好就是本组中1的个数.
然后我们把每2组合并成新的一组(现在每组的长度为2),可以得到
num= {a1,b1}{a2,b2}{a3,b3}{a4,b4}
我们的目的是希望计算新的每一组的和,那么具体步骤是
首先构造一个单组掩码g_mask,长度等于每组的长度,左半部分都为0,右半部分都为1,
也就是g_mask=01,(这个掩码的作用就是每一组的值和它进行与运算之后,只保留第2个元素,左半部分为0!)
然后把每组的g_mask合并起来得到一个总的掩码mask,即
mask=01010101=0x55
然后我们计算num&mask,这一步的目的是把每组左半部分的值为0,
即
num&mask = {0,b1}{0,b2}{0,b3}{0,b4}
然后我们要得到每一组的左半部分,并且要和右半部分对齐,以便相加,实现这一步的技巧为
首先计算 num>>s,其中s为组长的一半,即
num>>s = {a1,a1}{b1,a2}{b2,a3}{b3,a4}
然后再和mask进行与运算,以只保留右半部分,即
(num>>s)&mask = {0,a1}{0,a2}{0,a3}{0,a4}
然后我们计算
num&mask+(num>>s)&mask,这就相当于计算
{0,b1}{0,b2}{0,b3}{0,b4}
+{0,a1}{0,a2}{0,a3}{0,a4}
={a1+b1},{a2+b2},{a3+b3},{a4+b4}
然后对新的数据继续进行两两合并,
每次重新计算
n*=2; //两两合并就意味着每次分组长度扩大一倍,同时分组数目减少一半
s=n/2; //s始终为当前分组长度的一半,这样才能保证每组右移s位之后,左元素和右元素对齐,以便相加
g_mask=1< mask=n个g_mask连接起来,总长度为整数位长L right = num>>mask; //每组只保留右半部分,左半部分为0 left = (num>>s)&mask; //把每组的左半部分移动到右部 num = left+right; //计算每组的和,现在每组中的值都等于原始数据中对应那一组中的1的个数 直到最后只剩一组为止,循环结束.最后得到的结果即为所求 下面是一个具体的计算例子, 输入数据: L=整数位数=32 计算步骤(n和s每次都乘2) 原始分组 num = {1} {1} {1} {1} {1} {1} {1} {0} {0} {0} {0} {1} {0} {0} {1} {0} {1} {0} {0} {1} {1} {0} {1} {0} {1} {0} {1} {1} {1} {1} {0} {0} step1 将num两两合并得到 {1,1} {1,1} {1,1} {1,0} {0,0} {0,1} {0,0} {1,0} {1,0} {0,1} {1,0} {1,0} {1,0} {1,1} {1,1} {0,0} 现在分组长度n=2,移位长度为s=n/2=1,分组数为16,单组mask为01, 总mask为 16个二进制01即0x5555 5555 right=num&mask的结果为 {0,1} {0,1} {0,1} {0,0} {0,0} {0,1} {0,0} {0,0} {0,0} {0,1} {0,0} {0,0} {0,0} {0,1} {0,1} {0,0} num>>s的结果为 left=(num>>s) &mask的结果为 left+right的结果为 b={1,0} {1,0} {1,0} {0,1} {0,0} {0,1} {0,0} {0,1} {0,1} {0,1} {0,1} {0,1} {0,1}{1,0} {1,0} {0,0} step2 继续将b两两合并 得到 b={1,0,1,0} {1,0,0,1} {0,0,0,1} {0,0,0,1} {0,1,0,1} {0,1,0,1} {0,1,1,0} {1,0,0,0} 现在分组长度n=4,移位长度为s=n/2=2,分组数为8,单组mask为0011, 总mask为 8个二进制0011即0x33333333 然后计算 c= b&mask+(b>>s) &mask,得到 c=0100 0011 0001 0001 0010 0010 0011 0010 step3, 现在分组长度n=8,移位长度为s=n/2=4,分组数为4,单组mask为00001111, 总mask为 0x0f0f0f0f 计算d= c&mask+(c>>s)&mask 得到 d=0000 0111 0000 0010 0000 0100 0000 0101 step4. 现在分组长度n=16,移位长度为s=n/2=8,分组数为2,单组mask为0x00ff 总mask为 0x00ff00ff 计算e= d&mask+(d>>s)&mask 得到 e=0000 0000 0000 1001 0000 0000 0000 1001 step 5 现在分组长度n=32,移位长度为s=n/2=16,分组数为1,单组mask为0x0000ffff 总mask为 0x0000ffff 计算f= e&mask+(e>>s)&mask 得到 f=0000 0000 0000 0000 0000 0000 0001 0010 即10进制的18,现在分组数目为1,算法结束,f即为所求结果 下面是上面计算过程的C程序,供读者参考,并自行修改让其适应64位的情况 程序运行结果为 a=1111 1110 0001 0010 1001 1010 1011 1100 显然本算法的复杂度为log(2,L),L为机器的整数位长.
a为输入的二进制整数
a=1111 1110 0001 0010 1001 1010 1011 1100
一共18个1,预期输出18.
{0,1} {1,1} {1,1} {1,1} {0,0} {0,0} {1,0} {0,1} {0,1} {0,0} {1,1} {0,1} {0,1} {0,1} {1,1} {1,0}
{0,1} {0,1} {0,1} {0,1} {0,0} {0,0} {0,0} {0,1} {0,1} {0,0} {0,1} {0,1} {0,1} {0,1} {0,1} {0,0}#include
b=1010 1001 0001 0001 0101 0101 0110 1000
c=0100 0011 0001 0001 0010 0010 0011 0010
d=0000 0111 0000 0010 0000 0100 0000 0101
e=0000 0000 0000 1001 0000 0000 0000 1001
f=0000 0000 0000 0000 0000 0000 0001 0010
0xfe129abc has 18 ones