题目
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
解析
预备知识
世界上有10种人,一种人知道二进制,而另一种人不知道二进制。
在计算机中数都是采用二进制存放的,这与硬件设计电路(比如与门,放大器)等有关,且二进制只有2种状态,简单高效,故计算机采用二进制。
而参与运算的数在计算机一般用其补码形式存放。
补码是原码减1,除了符号位以外全部按位取反得到的结果。
而原码是符号位加上数值本身的二进制形式表达。
正数的原码与补码相同,比如2的原码为10,其实为0…10,不过前向0被我省略了,但在内存中这些0还是占相应的字节。
而-2的原码为1……10,-2的补码为1111…110。它是通过原码减一,然后除了符号位其余全部取反得到。
我们的四则运算其实在计算机底层都是位运算。位运算有:与,或,异或,左移,带符号右移和无符号右移。
与,或,异或,都是很简单运算,想必你们都会,我就不细说了。对于移位运算可以参考这篇博文,虽然说得是Java中标准,但大多数编程语言都大同小异。
思路一
我们按照移位操作对原数进行无符号右移(不明白看我推荐那个博文)操作,然后与1相与判断该位是否是1,从而决定是否计数,这样扫描一遍数的二进制形式,即可得到1的个数。
之所以采用的是特殊的移位操作——无符号右移,是因为这样才能对负数适用。因为负数的最高位即符号位为1,如果采用的普通的右移操作(即带符号右移),高位会在移位后不断填充1,这样会导致死循环。而采用无符号右移,则会补充0,当符号位最后右移消失后,整个数最终为0,则终止循环。
/**
* 普通做法,通过移位操作进行
* @param n
* @return
*/
public static int NumberOf1One(int n) {
int count = 0;
while(n != 0) {
if((n & 1) == 1) {
count++;
}
n >>>= 1;
}
return count;
}
思路二
当然也不用搞这个比较难懂的无符号右移,我们可以把数与1做与运算,从而判断该位是1,然后把1左移1位,即得到2。再把数与2做与运算,从而判断倒数第二位是否是1。重复左移,直到进行32次,因为int型为32位。
/**
* 左移辅助变量
* @param n
* @return
*/
public static int NumberOf1Three(int n) {
int count = 0;
int t = 1;
for(int i = 1; i <= 32; i++) {
if((n & t) != 0) {
count++;
}
t <<= 1;
}
return count;
}
思路三
以上二种做法都是按位扫描,复杂度为O(n)。有没有最好的方法呢?当然有。我们首先把原数做减一操作,看2种情况(数不为0的情况下):
1. 当最后一位是1,减去1后,该位置为0,其余位置保持不变。比如7(111) - 1 = 6(110)
2. 当最后一位不是1,假设第m位为该数二进制的最右边的1,减去1后,第m位变为0,但是第m位之后直到末尾全部变了1。比如12(1100)- 1 = 11 (1011)
。
通过上面发现,对原数做减一操作,都可以使数的最右边的1变为0。如果是第一情况,该数其他位置不变,但是如果是第二情况,则会导致m位之后变为1,那如何使m位之后还是以前的0呢,做与操作。原数与减一后的数做与操作。即可做到m位之前不变,m位之后也与原数相同(即都为0)。
结论为:把一个整数减去1后与原数做与操作,得到的结果为整数的二进制形式的最右边的一个1变为0。
所以如果原数有n个1,我们只需进行n次这样的减一和与操作即可了。Amazing!
/**
* n &= n - 1
* @param n
* @return
*/
public static int NumberOf1Two(int n) {
int count = 0;
while(n != 0) {
count++;
n &= n - 1;
}
return count;
}
拓展
拓展1
判断一个整数是不是2的整数次幂。
只需判断是否数的二进制形式中是否只有1个1即可。
/**
* 判断一个整数是否是2的整数次方
* @param num
* @return
*/
public static boolean isTwoTimes(int num) {
return (num & (num - 1)) == 0;
}
拓展2
给定2个整数,判断最少改变第一个数的二进制中位即可与第二个数相同。
异或即可求出不同位置,因为不同位置异或为1,然后再统计1的个数即可。
public static int countOfChange(int num1, int num2) {
int res = num1 ^ num2;
int count = 0;
while(res != 0) {
count++;
res &= res - 1;
}
return count;
}