题目描述:输入一个整数,输出该整数二进制表示中1的个数。其中负数用补码表示。
- 输入:输入可能包含多个测试样例。对于每个输入文件,第一行输入一个整数T,代表测试样例的数量。对于每个测试样例输入为一个整数。n保证是int范围内的一个整数。 输出:对应每个测试案例, 输出一个整数,代表输入的那个数中1的个数。 样例输入:
-
345-1
样例输出: -
1232
这个题目前面在微软的编程之美上面就看过,今天在九度上面又碰到了。很多题目看着简单,自己写起来就不是这么容易啊。特别是正整数的还好,负整数的我竟然没有写出来。
首先考虑正整数,对于一个a,如果能够被2整除,说明该位为0,不能够被2整除,该位就为1,这样就能很快计算出1的个数。
while(a){
if(a%2!=0)
count++;
a=a/2;
}
但是除法的效率比较低,其实可以将a与1做&操作,可以判断a的最低位是否为1,然后将a依次右移,判断每一位是否为1,效率显然高点。因为移位操作比除法效率高多了。
while(a){
if(a&1==0)
count++;
a=a>>1;
}
但是这个算法还是存在问题,正整数可以,负整数就不行了。当输入的a是一个负数时,不但不能得到正确的1的个数,还将导致死循环。以负数0x80000000为例,右移一位的时候,并不是简单地把最高位的1移到第二位变成0x40000000,而是0xC0000000。这是因为移位前是个负数,仍然要保证移位后是个负数,因此移位后的最高位会设为1。如果一直做右移运算,最终这个数字就会变成0xFFFFFFFF而陷入死循环。为了避免死循环,我们可以不右移输入的数字a。首先a和1做与运算,判断a的最低位是不是为1。接着把1左移一位得到2,再和a做与运算,就能判断a的次高位是不是1……这样反复左移,每次都能判断a的其中一位是不是1。基于此,我们得到如下代码:
int NumberOf1_Solution2(int a) { int count = 0; unsigned int flag = 1; while(flag) { if(a & flag) count ++; flag = flag << 1; } return count; }
另外一种思路是如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减去1,那么原来处在整数最右边的1就会变成0,原来在1后面的所有的0都会变成1。其余的所有位将不受到影响。举个例子:一个二进制数1100,从右边数起的第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成1,而前面的1保持不变,因此得到结果是1011。 我们发现减1的结果是把从最右边一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000。也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0。那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。 这种思路对应的代码如下:
int NumberOf1_Solution3(int i) { int count = 0; while (i) { ++ count; i = (i - 1) & i; } return count; }
扩展:如何用一个语句判断一个整数是不是二的整数次幂? PS:n&(n-1)==0;//二进制数只有一位位1,则该数是2的整数次幂.