题目
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
方法一
最容易想到的就是每次去判断二进制的最右边一位是不是1,然后右移一位,直到整个数的二进制变为0。那么要怎么判断最后一位是不是0呢,只需要和1进行与操作。
这个方法对于整数直接有效,但是负数的话,因为符号位是1,右移的时候最高位会补1,那么这个数永远不会为0,无法退出,进入死循环,我们可以直接将负数变成整数, 最后在结果上直接+1即可。
代码一
public class offer11 {
public static void main(String[] args) {
offer11 offer111 = new offer11();
System.out.println(offer111.NumberOf1(5));
}
public int NumberOf1(int n) {
if (n == 0) {
return 0;
}
int temp = n;
int count = 0;
if (temp < 0) {
temp = temp * (-1);
}
while (temp != 0) {
if ((temp & 1) == 1) {
count ++;
}
temp = temp >> 1;
}
return n> 0 ?count : (count+1);
}
}
在eclipse里面亲测有效,但是牛客网测试无法通过:

方法二
在方法一里面,我们是移动需要判断的数,导致了需要去进行正负数的不同处理,其实我们可以每次第一次判断最右边是不是1,然后从右到左第二位第三位, 只需要分别和0000 0001,0000 0010,0000 0100…..相与,也就是说我们可以每次将1左移一位,直到这个数变 == 0;
代码二
public int NumberOf2(int n) {
if (n == 0) {
return 0;
}
int flag = 1;
int count = 0;
while (flag != 0) {
if ((n & flag) != 0) {
count +=1;
System.out.println(count);
}
flag = flag << 1;
}
return count;
}
这个在牛客网测试会通过
方法三
其实方法二的复杂度也不是很高,只需要循环32次,但是不知道为啥牛客网不能通过。
举例子,6的二进制表示为0000 0110(一般都是用8位表示原码)。6-1,二进制的表示为:0000 0110 - 0000 0001,这个时候,遇到0000 0110从右到左, 第一个不为0的位,将其变为0,而这个位右边的全部变为1,也就是0000 0101。然后将0000 0101 与6 的原码进行“与”运算,即 0000 0101 & 0000 0110, 得到0000 0100,这样进行一次之后就将6的原码的最右边的1变为了0。按照这个思想,我们一直进行此过程,直到n等于0。
代码三
public int NumberOf3(int n){
if(n == 0){
return 0;
}
int countOfOne = 0;
while(n != 0){
n = n & (n - 1);
countOfOne ++ ;
}
return countOfOne;
}
原码 补码
所谓原码就是机器数,是加了一位符号位的二进制数,正数符号位为0,负数符号位为1,计算机中存储、处理、运算的数据通常是8位、16位、32位或64位的。 这里以最简单的8位为例讲解。注意符号位是包含在8位中的其中1位,故可直观读出的数只有7位(只有后7位数可以按权展开)。有心人可能注意到原码是有缺陷的, 它只能表示255种状态,因为00000000(+0)和10000000(-0)其实是一个数,因此原码的表示范围成了-127到+127,这个问题需要神奇的补码来解决,因为在补码中10000000被用来表示-128。
所谓反码,英语里又叫ones’ complement(对1求补),这里的1,本质上是一个有限位计数系统里所能表示出的最大值,在8位二进制里就是11111111,在1位十进制里就是9,在3位十六进制里就是FFF(再大就要进位了)。求反又被称为对一求补,用最大数减去一个数就能得到它的反,很容易看出在二进制里11111111减去任何数结果都是把这个数按位取反,0变1,1变零,所以才称之为反码。用原码求反码的方法是,正数不变,负数保留符号位1不变,剩下位按位取反。
所谓补码,英语里又叫two’s complement(对2求补),这个2指的是计数系统的容量(模),就是计数系统所能表示的状态数。对1位二进制数来说只有0和1两种状态,所以模是10也就是十进制的2,对7位二进制数来说就是10000000,这个模是不可能取到的,因为位数多一位。用模减去一个数(无符号部分)就能得到这个数的补,比如10000000-1010010=0101110,事实上因为10000000=1111111+1,稍加改变就成了(1111111-1010010)+1,所以又可以表述为先求反再加1。总结求补码的方法就是正数依旧不变,负数保留符号位不变,先求反码再加上1。记住了怎么求补码,接下来讲讲运算。通过原码的符号位和数值,我们能迅速指出它代表的数,判断其正负并进行四则运算,相比而言反码和补码对于人则显得过于晦涩。如果说原码是给人看的数字语言,那么补码就是计算机的数字语言。计算机不需要知道什么是正负、大小,这些判断对它而言过于复杂。事实上它存储、处理、传输的数都只有补码一种形式,人所做的加减乘除,在计算机里只通过相加和移位就能解决,这都来自于补码系统的内在自洽和巧夺天工的神奇魔力,也是后文要阐述的重点。
参考文章:https://www.zhihu.com/question/23172611/answer/119406298