题目来源:牛客网-剑指Offer专题
题目地址:二进制中1的个数
题目描述
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
题目解析
对于这种涉及位运算的题目,我们首先要了解基本的位运算,我们可以看这篇文章:C++描述的位运算总结
(PS:本文的图例用的是8位的整数,int有32位,画出来也不美观呀)
下面先给说一种因为存在负数而错误的做法:
- 先通过 ‘&1’ 操作来判断 n n n 的最低位否为1,若为1,则计数器 c n t cnt cnt 的值加1;
- 然后利用 ‘>>1’ 操作将 n n n 右移一位;
- 最后重复上述两步操作,直到 n n n 的值为0,得出 c n t cnt cnt 的值即为该数二进制表示中1的个数。
这种方法在
n
n
n 为正数的时候是没有问题的,因为正数进行右移操作的时,最低位舍弃,最高位补0。过程如图所示:
但是负数进行右移操作的时,最低位舍弃,最高位的是1(过程如下图),这样经过多次以后,所有数位都会变成1,进而产生死循环。
下面给大家一共四种不同的正确解法:
方法一:
利用Integer内置的toBinaryString()方法将
n
n
n 转换为二进制字符串,然后二进制串中的0全部消掉,最后求得的字符串长度就是该数二进制表示中1的个数。
public class Solution {
public int NumberOf1(int n) {
return Integer.toBinaryString(n).replaceAll("0", "").length();
}
}
方法二:
这是对上面讲到的错误方法的改进,由于左移操作是最高位舍弃,最低位补0,我们选择让
n
n
n 不变, 对 ‘&1’ 中的1进行左移,直到移动31位为止。
由上图的两种情况可以看出,当1移动了
i
i
i 位之后,若
n
n
n 的第
i
i
i 位为1,进行&运算的结果为
(
1
<
<
i
)
(1 << i)
(1<<i),否则为0。于是,我们就可以利用他们为判断条件来计算1的个数啦~
public class Solution {
public int NumberOf1(int n) {
int cnt = 0;
for(int i = 0; i < 32; i++) {
//if ((n & (1 << i)) == (1 << i))也是可行的
if ((n & (1 << i)) != 0)
cnt++;
}
return cnt;
}
}
方法三:
这种方法是很久之前zls师兄告诉我的,没想到今天才派上用场(我记忆力真好 )。
理论: n n n 和 n − 1 n-1 n−1 进行 & \& & 运算可以把 n n n 的二进制表示的右边第一个1变成0
根据上面给的理论,我们只要知道
n
n
n 可以进行多少次这样的操作,就可以得到
n
n
n 的二进制表示中1的个数,下图以n=29为例展示这个过程:
如果对上诉过程感受不深,可以手动计算多几组数据,虽然这种方法感觉起来比较笨,但却是理解的好办法。能用上这种方法,面试应该没问题了。
public class Solution {
public int NumberOf1(int n) {
int cnt = 0;
while (n != 0) {
n = n & (n - 1);
cnt++;
}
return cnt;
}
}
方法四:
这是在牛客网评论区看到做法,由Holiday_12138发表,但是ta并没有给出具体思路,我也参悟不出来,我打算以后理解了再更新这部分,也欢迎各位大佬提供解释。(做法真的太骚了 )
public class Solution {
public int NumberOf1(int n) {
int temp = n;
temp = (temp & 0x55555555) + ((temp & 0xaaaaaaaa) >>> 1);
temp = (temp & 0x33333333) + ((temp & 0xcccccccc) >>> 2);
temp = (temp & 0x0f0f0f0f) + ((temp & 0xf0f0f0f0) >>> 4);
temp = (temp & 0x00ff00ff) + ((temp & 0xff00ff00) >>> 8);
temp = (temp & 0x0000ffff) + ((temp & 0xffff0000) >>> 16);
return temp;
}
}
如果本文对你有所帮助,要记得点赞哦~