leetcode 338.比特位计数
题干
给定一个非负整数 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)来执行此操作。
题解
说道二进制1计数,首先想到的肯定是c++的内置方法__builtin_popcount
class Solution {
public:
vector<int> countBits(int num) {
vector<int> ans;
for(int i = 0 ; i <= num ; ++i){
ans.push_back(__builtin_popcount(i));
}
return ans;
}
};
尝试用动规嗯找了一波规律,结果发现找出来的规律只适用于num<=87的情况,GG
class Solution {
public:
vector<int> countBits(int num) {
vector<int> dp(num + 1,0);
dp[0] = 0;
int rangeBound = 2;
for(int i = 1 ; i <= num ; ++i){
if(i == rangeBound){
dp[i] = 1;
rangeBound *= 2;
continue;
}
if(i % 2 == 1){
dp[i] = dp[i - 1] + 1;
}else{
//偶数的情况
if((i - 6) % 4 == 0){
//维持前一位的情况
dp[i] = dp[i - 1];
}else{
//下降的情况,此时i一定是4的倍数,其中二幂的i已经被最开始的if短路了
//innerIndex用来标记该次下降在两个二幂之间属于偶数组中的第几组(4个数一组)
int innerIndex = (i - rangeBound / 2) / 4 + 1;
if(innerIndex % 2 == 1){
//如果组号是奇数,下降和组号有关
if(innerIndex <= rangeBound / 2 / 4 / 2 + 1){
//左半边的情况
dp[i] = dp[i - 1] - innerIndex / 2;
}else{
//右半边的情况
dp[i] = dp[i - 1] - (rangeBound / 2 / 4 - innerIndex + 1) / 2;
}
dp[i]--;
}else{
//如果组号是偶数,下降1
dp[i] = dp[i - 1] - 1;
}
}
}
}
return dp;
}
};
/*
0 - 0
1 - 1 -1
2 - 10 - 1
3 - 11 - 2
4 - 100 - 1
5 - 101 - 2
6 - 110 - 2
7 - 111 - 3
8 - 1000 - 1
9 - 1001 - 2
10 - 1010 - 2
11 - 1011 - 3
12 - 1100 - 2
13 - 1101 - 3
14 - 1110 - 3
15 - 1111 - 4
16 - 10000 - 1
17 - 10001 - 2
18 - 10010 - 2
19 - 10011 - 3
20 - 10100 - 2
21 - 10101 - 3
22 - 10110 - 3
23 - 10111 - 4
24 - 11000 - 2
25 - 11001 - 3
26 - 11010 - 3
27 - 11011 - 4
28 - 11100 - 3
29 - 11101 - 4
30 - 11110 - 4
31 - 11111 - 5
32 - 100000 - 1
33 - 100001 - 2
34 - 100010 - 2
35 - 100011 - 3
36 - 100100 - 2
37 - 100101 - 3
38 - 100110 - 3
39 - 100111 - 4
40 - 101000 - 2
41 - 101001 - 3
42 - 101010 - 3
43 - 101011 - 4
44 - 101100 - 3
45 - 101101 - 4
46 - 101110 - 4
47 - 101111 - 5
48 - 110000 - 2
49 - 110001 - 3
50 - 110010 - 3
51 - 110011 - 4
52 - 110100 - 3
53 - 110101 - 4
54 - 110110 - 4
55 - 110111 - 5
56 - 111000 - 3
57 - 111001 - 4
58 - 111010 - 4
59 - 111011 - 5
60 - 111100 - 4
61 - 111101 - 5
62 - 111110 - 5
63 - 111111 - 6
64 - 1000000 - 1
1223 2334 2334 3445 2334 3445 3445 4556
12 23 / 23 34 / 23 34 / 34 45
1 2 1 3 1 2 1
*/
从之前找规律的动规失败中总结经验,问题主要出在下降时的情况,其实这里只要利用二进制的性质,就根不不用做出很多抽象的判断。
核心即是二进制减法。
我们以2的幂为分界来把数字进行分组,拿[32,64)举例,32的二进制表示是100000。我们任取区间内任意数,比如58,58的二进制表示为111010,相较于32多了较低五位(11010)上的3个1,而两数二进制相差的部分11010十进制表示即为26,即58 - 32。
所以可以得到状态转移方程dp[i] = dp[rangeBound / 2 - i] + 1 此处rangeBound为数所在二幂区间的上界。
这个规律在区间内具有普适性,因此之前单独拿来判断的i为奇数时的情况也可以直接归入。
如此便可以得到:
class Solution {
public:
vector<int> countBits(int num) {
vector<int> dp(num + 1,0);
dp[0] = 0;
int rangeBound = 2;
for(int i = 1 ; i <= num ; ++i){
if(i == rangeBound){
dp[i] = 1;
rangeBound *= 2;
continue;
}
dp[i] = dp[i - rangeBound / 2] + 1;
}
return dp;
}
};