剑指Offer_#15_二进制中1的个数
Contents
题目
请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
示例 1:
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
示例 2:
输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。
示例 3:
输入:11111111111111111111111111111101
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。
思路分析
方法1:循环移位计数
算法流程
- 初始化计数变量res=0
- 循环右移位并判断每一位是否是1,当n=0时跳出
* n与1进行与运算,相当于取出当前的末位数,如果结果为1,res增加1
* n右移移位(注意在java中需要使用无符号右移>>>) - 返回结果
编码时要注意的点
&
操作符与==
操作符的优先级问题
由于==
操作符的优先级大于&
操作符,所以在进行判断时,需要在与运算外加括号,即
if ((n & 1) == 1) res += 1;//由于&的优先级低于==,n & 1外边需要加括号
- 有符号数和无符号数
题目要求我们将输入的n看作无符号数,即没有符号位,只能表示0或者正数。
但是在java中却是将输入的n看作有符号数的,java中的基本类型都是有符号数,也就是将第一位作为符号位,1表示负数,0表示正数。
所以在代码中有两个地方需要特别注意:- 循环条件需写成
n != 0
,而非n > 0
,因为输入的测试用例中有一些以1开头的,java编译器将其视为负数,所以如果用n > 0
作为判断条件,输入以1开头的数字,输出总是为0,是错误的。 - 右移操作,应该使用无符号右移操作符
>>>
而非普通右移操作符>>
- 右移
>>
:该数对应的二进制码整体右移,左边的用原有标志位补充,右边超出的部分舍弃。 - 无符号右移
>>>
:不管正负标志位为0还是1,将该数的二进制码整体右移,左边部分总是以0填充,右边部分舍弃。
如果使用普通右移,对于负数,会在左边补1,那么就增加了原本数字里边1的个数。这样一来,永远都会有新的1出现,循环无法跳出,提交结果是超出时间限制。
- 右移
- 循环条件需写成
方法2:n & (n-1)
(n−1) 解析: 二进制数字 n 最右边的 1变成 0 ,此 1 右边的 0 都变成 1 。
n&(n−1) 解析: 二进制数字 n 最右边的1 变成 0 ,其余不变。
也就是,每次进行n & (n - 1)运算,就可以消去最后一位的1。
通过循环,每一次消去一位1,就计数一次,最后n变为0,循环结束。
解答1:循环移位计数
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int res = 0;
while(n != 0){//这里条件写为n > 0就不能通过,因为如果首位为1,那么这是一个负数,不会进入循环
if ((n & 1) == 1) res += 1;//由于&的优先级低于==,n & 1外边需要加括号
n = n >>> 1;//>>>的意思是右移之后,左边补零
}
return res;
}
}
复杂度分析
时间复杂度为,因为输入n,对应的二进制数位数是。
空间复杂度为,使用额外变量res
。
解答2:n & (n - 1)
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int res = 0;
while(n != 0){
n = n & (n - 1);
res++;
}
return res;
}
}
复杂度分析
时间复杂度为,M为1的个数。
空间复杂度为,使用额外变量res。