Medium | LeetCode 338. 比特位计数 | 动态规划 + 位运算
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)来执行此操作。
方法一:对每个数字计算Pop Count(汉明权重)
计算一个数字的二进制位数的方法如 Easy | LeetCode 191 | 剑指 Offer 15. 二进制中1的个数 | 位运算 所示。通过 n & (n - 1)
可以把最右边的1变成0, 通过计算 n&(n-1) 操作的次数即可得到二进制中1的个数。
public int[] countBits(int num) {
int[] res = new int[num+1];
for (int i = 0; i <= num; i++) {
res[i] = popCount(i);
}
return res;
}
private int popCount(int num) {
int res = 0;
while (num != 0) {
res++;
num &= (num - 1);
}
return res;
}
方法二: 动态规划
如方法一可以知道 数字 n 的二进制里1的位数, 比 n&(n-1) 的二进制位数 多1, 并且 n > n &(n-1), 则可以得到动态规划转移方程。
\[P(x)=P(x \&(x-1))+1 ;
\]
利用此动态转移方程, 可以得到如下的代码
public int[] countBits(int num) {
int[] ans = new int[num + 1];
for (int i = 1; i <= num; ++i)
ans[i] = ans[i & (i - 1)] + 1;
return ans;
}
方法三:
已经某个数n的二进制1的位数, 通过将n的 高位每一位尝试设置为1 的方式, 可以得到下面的转移方程。
\[P(x+b)=P(x)+1, b=2^{m}>x
\]
依据此状态转移方程, 可以得到如下的代码:
public int[] countBits(int num) {
int[] ans = new int[num + 1];
int i = 0, b = 1;
// [0, b) is calculated
while (b <= num) {
// generate [b, 2b) or [b, num) from [0, b)
while(i < b && i + b <= num){
ans[i + b] = ans[i] + 1;
++i;
}
i = 0; // reset i
b <<= 1; // b = 2b
}
return ans;
}
方法四:
通过将n最低位(2^0位)的数字抹掉(抹掉之后, 其二进制个数与n>>1相同), 可以得到如下的状态转移方程。
\[P(x)=P(x / 2)+(x \quad \bmod 2)
\]
public int[] countBits(int num) {
int[] ans = new int[num + 1];
for (int i = 1; i <= num; ++i) {
ans[i] = ans[i >> 1] + (i & 1);
}
// x / 2 is x >> 1 and x % 2 is x & 1
return ans;
}