本文参考书籍 《剑指offer》 作者何海涛
01 题目
请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。例如:9表示成二进制为1001,有两位是1,因此输入9该函数输出2。
02 相关知识点回顾
- 与运算&: 1 & 1=1 , 1 & 0=0, 0 & 0=0
- 或运算|: 1 | 1 = 1, 1 | 0 = 1 , 0 | 0=0
- 非运算^: ^1=0 , ^0=1
- 右移运算:(1)正数二进制右移运算,正数左边补充0,右边减少一位。
(2)负数二进制右移运算,左边补充1,右边减少一位。(计算机底层数值表示中第一位是符号位,用1表示负数,为了保持负数性质,右移左边补充1)
- 原码,反码,补码:(1) 原码,某个数的二进制表示 (2)反码,原码符号位除外其他数值取反。如java里面的整型32位的最高位,其余31位取反 从0到1,或者从1到0。(3)补码=反码+1
- 正数的原码,反码,补码完全相同,即都是自身。
- 在计算机中,负数都是采用补码表示的。
03 解法1
将输入的数值和1进行与运算
if n&1 ==0 说明最后一位为0,if n&1 != 0 说明最后一位为1。
之后右移数值,去掉最右面的一位,循环判断。
结束条件 右移到n为0
![ed3a4c8353c5160ce0eed641f041d56f.png](https://i-blog.csdnimg.cn/blog_migrate/7d1f38873955877215534dd5efac55c7.jpeg)
这种解法对于正数可以给出正确的答案,如果对于负数,则会造成死循环,如-1,它的计算机二进制(补码形式)表示为 1111 1111 1111 1111 1111 1111 1111 1111,那么右移一位左面补充为1,n永远不能为0
System.out.println(0b11111111111111111111111111111111);// 计算机表示的二进制-1
04 解法2
取变量flag=1进行左移,然后与数值进行&运算,如果不为0,说明该位为1,否则为0
![4bea2b8b48d0fedf61213f25adc49158.png](https://i-blog.csdnimg.cn/blog_migrate/8a1ba94e55e03c9738b1effa57c7062d.jpeg)
结束条件:左移直到flag唯一的1被移没,此时flag==0 ,所以flag!=0为while循环条件
![ebaaf8af043f167f4b26be87ecc24556.png](https://i-blog.csdnimg.cn/blog_migrate/5db9b659f9c2538d7ec51250cfc23d2f.jpeg)
如果是int型数值,不管是数值为几,每次都移动32次结束循环
05 解法三
先说结论:n&n-1得到的值为n最后一位1变为0,所以每次通过n=n&(n-1)减少n中1的个数,直到n中1的个数为0,n == 0 结束循环。
推理过程:数值的二进制表示中,最后一位要么为1,要么为0
(1)最后一位为1,那么减1,就把最后一位的1变为0,此时与n 做与运算,结果除了最后一位变为0之外,其他位数值都不变。如 10011 & 10010=10010
![f8688c33c63e73e9cf005b5fd65b5e63.png](https://i-blog.csdnimg.cn/blog_migrate/ac77ec9e4a14b0f8e45bb8c35a94fbc1.jpeg)
(2)最后一位为0, 如11100, 减一之后,变为 11011 ,假设最右边的1的位置为m ,那么11100减1之后m位的1变为0,m位右边的0都变为1
![109be41216aa7cba3a8404c8b228674e.png](https://i-blog.csdnimg.cn/blog_migrate/42271628f3f798c9d5202218e7105541.jpeg)
所以 11100 & 11011=11000 把 最右面的一位1消掉。消掉多少个1就是这个n中总共有多少个1
![7ffec61e2d262741269c01ec51f2b219.png](https://i-blog.csdnimg.cn/blog_migrate/f03c5676fe2247eb8427bafe3797e56f.jpeg)
06 负数的结果
输入比如-1,结果是32,因为负数在计算机中以补码的形式表示,所以-1的二进制形式为
System.out.println(0b11111111111111111111111111111111);// 0b表示二进制
其他负数结果类似