题目:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。右移一位
首先我们第一个想到的是将该整数和1做&运算如果等于1则计数加1,然后右移,直到右移后该数为0为止
代码实现:
public int NumberOf1(int n) {
int count=0;
while(n!=0)
{if((n&1)==1)
{
count++;
}
n=n>>1;//不选除法运算而选择移位运算是因为移位运算比除法运算效率高
}
return count;
}
这种解法存在一个缺陷就是,当输入负数时,会引起死循环。因为负数在做移位运算的时候计算机为了保证该数还是一个负数,他会在前面补一个一。例如:负数1101做移位运算后是1110
那么怎么避免死循环呢?我们可以把移位对象换成1,也就是每循环一次之后我们就把1左移一位,如果1个输入的整数相$不等于0,那么说明整数二进制对应的1所在的位置处是1,当移到0时循环结束,也就是1溢出了.
例如:输入一个整数6那么他的二进制就是00000000 00000000 00000000 00000110,开始1和00000000 00000000 00000000 00000110做&运算(其实就是和00000000 00000000 00000000 00000110的最后一位做&)得到的是0,接着把1左移得10,将得到的10和00000000 00000000 00000000 00000110做&运算(其实就是和00000000 00000000 00000000 00000110的倒数第二位做&)得到10,以此类推最后1被移位成1 00000000 00000000 00000000 00000000,将1舍去了(超过了32位)实现的代码如下
代码:
public int NumberOf1(int n) {
int count = 0;
int flag = 1;
while (flag != 0) {
System.out.println(n&flag);
if ((n & flag) != 0) {
count++;
}
flag = flag << 1;
}
return count;
}
这种方法也不是最优解,因为他需要1移位32次,所以我们需要想出一种办法,让他循环最少。
我们来思考一下如果将一个数减去1,它的二级制会变成什么
整数14,二进制1110
14-1,二进制1101
整数15,二进制1111
15-1,二进制1110
从上面我们可以总结出当最后一位为0时,将该数减1,它最后一个1就变成了0,最后一位1的后边的所有0变成了1.
当最后一位是1时,该数的最后一位变为0,前边所有都不变。
那么我们怎么利用这个来计算出有多少个1呢?
道理很简单我们逐个屏蔽掉最后一个1,屏蔽了多少次,就有多少个1。
举个栗子:1111-1 = 1110
接下来我们只需要算1110有多少个1就好了,那么怎么得到1110呢?
我们思考一下将1111和1110做&得到了什么
答案是1110
这样问题就解决了,让我们再顺一下思路。
思路:将输入的整数n与n-1做&运算,将结果赋给n,就是n=n&(n-1)直到n=0结束,能做几次就有几个1。
最优代码:
public int NumberOf1(int n) {
int count=0;
while(n!=0)
{
++count;
n=(n-1)&n;
}
return count;
}