面试题3:前n个数字二进制中1的个数
力扣链接:https://leetcode-cn.com/problems/w3tCBm/
题目描述
给定一个非负整数n,请计算0到n之间的每个数字的二进制表示中1的个数,并输出一个数组。
0 <= n <= 105
样例输入1
n = 2
样例输出1
[0,1,1]
解释:
0 ----> 0
1 ----> 1
2 ----> 10
样例输入2
n = 5
样例输出2
[0,1,1,2,1,2]
解释
0 ----> 0
1 ----> 1
2 ----> 10
3 ----> 11
4 ----> 100
5 ----> 101
解析
最先想到的方法应该就是:使用一个for循环计算从0到n的每个整数的二进制形式中1的个数。于是问题就转化为如何求解一个整数二进制形式中1的个数。
第一种方法
用“i & (i-1)” 将整数最右边的1变成0。
"i-1"会把整数i最右边的1变成0,如果它的右边还有0,则右边的所有0都变成1,左边的所有位都保持不变。以二进制的1100为例,它减去1的结果是1011。1100和1011的位运算是1000。二进制的1100最右边的1变成0,结果刚好就是1000;
class Solution {
public:
vector<int> countBits(int n) {
vector<int> res(n+1,0);
for(int i=0;i<=n;i++){
int j=i;
while(j!=0){
res[i]++;
j=j&(j-1);//去掉最右边的1
}
}
return res;
}
};
第二种方法
用“i&1”判断整数i的最后一个位置是0还是1,每判断一次就让"i>>1",消去刚刚判断过的那一位,重复上述过程直到i变成0;
class Solution {
public:
vector<int> countBits(int n) {
vector<int> res(n+1,0);
for(int i=0;i<=n;i++){
int j=i;
while(j!=0){
if(j&1)res[i]++;
j>>=1;
}
}
return res;
}
};
不管这两种方法用哪种方法,时间复杂度都是O(nk)其中k是整数二进制形式中1的个数。
再仔细一点就会发现其实我们做了很多的重复工作。比如,在第一种方法中,因为“i&(i-1)”是把整数i的最后一个1变成0,所以在计算i中有多少个1的时候可以先计算出"i&(i-1)"中有多少个1然后再加上1;第二种方法,可以先计算出i>>1中有多少个1,然后在判断i最后一个位置是不是1。对于上述的情况,可以用一个数组保存前面算过的结果。
第一种方法的优化
class Solution {
public:
vector<int> countBits(int n) {
vector<int> res(n+1);
res[0]=0;
for(int i=1;i<=n;i++){
res[i]=res[i&(i-1)]+1;
}
return res;
}
};
第二种方法的优化
class Solution {
public:
vector<int> countBits(int n) {
vector<int> res(n+1);
res[0]=0;
for(int i=1;i<=n;i++){
res[i]=res[i>>1]+(i&1);
}
return res;
}
};
需要注意的就是,按位与&的优先级没有加法+的优先级高,所以需要把i&1加上括号