题目大意
给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。
示例 1:
输入: 2
输出: [0,1,1]
示例 2:
输入: 5
输出: [0,1,1,2,1,2]
进阶:
- 给出时间复杂度为O(n*sizeof(integer))的解答非常容易。但你可以在线性时间O(n)内用一趟扫描做到吗?
- 要求算法的空间复杂度为O(n)。
- 你能进一步完善解法吗?要求在C++或任何其他语言中不使用任何内置函数(如 C++ 中的 __builtin_popcount)来执行此操作。
解题思路
方法一:
将每个数字的结果依次写出–> [0,||1,||1,2,||1,2,2,3,||1,2,2,3,2,3,3,4,||1,2,2,3,2…]。
我们可以将数字分组,每组的数量是2的幂(除了第一个数)。同时,每组数的前半部分是上一组数,每组数的后半部分是上一组数+1。这不难理解,6(110)可以看做是在2(10)的前面多放置了一个1,7(111)可以看做是在3(11)的前面多放了一个1.
class Solution {
public:
vector<int> countBits(int num) {
if (n == 0)
return {0};
if (n == 1)
return {0, 1};
vector<int> res(num + 1, 0);
res[1] = 1;
num = num - 1;
// 我们从第二组数开始计算,因此2^index是该组的长度,numIndex是起始位置
int index = 1, numIndex = 2;
while (num > 0){
// 计算当前组的长度,即2^index
int curTotalNums = 1 << index;
// 将该组分为前后两部分,前一部分等于前一组的结果,后一部分是前一组的结果+1
int half = curTotalNums / 2, left = numIndex - half;
// 如果该组所有数字都在给定范围内
if (curTotalNums <= num){
// 计算前半组
for (int i = 0; i < half; ++i){
res[numIndex++] = res[left++];
}
// 计算后半组
for (int i = 0; i < half; ++i){
res[numIndex++] = res[left++] + 1;
}
// 缩小范围
num -= curTotalNums;
// 下一轮的范围变大
++index;
}
// 如果当前组超出了给定范围,但是当前组的前半部分在范围内
else if (half <= num){
// 计算前半组
for (int i = 0; i < half; ++i){
res[numIndex++] = res[left++];
}
// 后半组就剩了有限个,即num-half个数字
for (int i = 0; i < num - half; ++i){
res[numIndex++] = res[left++] + 1;
}
break;
}
// 如果当前组超出范围,且前半部分也超出范围
else{
// 计算在范围内的数字
for (int i = 0; i < num; ++i){
res[numIndex++] = res[left++];
}
break;
}
}
return res;
}
};
方法二:
前面说到过,6(110)可以看做是在2(10)的前面多放置了个1,7(111)是在3(11)的前面多放了个1。
换一种角度考虑,我们也可以认为6(110)是通过4(100)变的。即将4的二进制形式中的最后一个1后面的0变成1产生的。
因此我们可以先将某个数字num的二进制形式下最后一个1消掉,得到一个新的数字newnum,然后找到该数字的结果,在此结果上+1即可。
注意,因为我们将num中的最后一个1变成了0,因此newnum一定小于num。所以可以通过newnum得到num的结果。
class Solution {
public:
vector<int> countBits(int num) {
vector<int> res(num + 1, 0);
for (int i = 1; i <= num; i++)
// i & (i - 1)表示消掉i的最后一个1
res[i] = res[i & (i - 1)] + 1;
return res;
}
};