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. 动态规划-低位有效判断
我们先来看这样一个例子
1111,1110他们的bits取决于
111的bits + 末位是否为1
1101,1100他们的bits取决于
110的bits + 末位是否为1
很容易可以得到递推公式:
bits[i] = bits[i >> 1] + (i & 1)
公式解释:
- bits[i] 表示第i个数的bit为1的个数
- i >> 1,表示i右移1位 可以把1110,1111变为 111,简单理解就是舍掉最低1位
- i & 1,与运算。将第i个数与1作与运算,如果末位为1,则i & 1的结果就是1,否则0。
代码
func countBits(num int) []int {
bits := make([]int, num+1)
for i := 1; i <= num; i++ {
bits[i] = bits[i>>1] + (i & 1)
}
return bits
}
2. 动态规划-设置最低有效位
1 & 0 = 001 & 000 = 0 = 000
2 & 1 = 010 & 001 = 0 = 000
3 & 2 = 011 & 010 = 2 = 010
4 & 3 = 100 & 011 = 0 = 000
5 & 4 = 101 & 100 = 4 = 100
6 & 5 = 110 & 101 = 4 = 100
7 & 6 = 111 & 110 = 6 = 110
8 & 7 = 1000 & 0111 = 0 = 0000
不难看出一个规律
n & n-1 得到一个数,这个数就是将n 的最末位的1变成0的结果所以我们可以得到这样一个递推公式:
bits[i] = bits[i & [i-1]] + 1
公式解释:
- i & i-1 将i的最末位1 变成了0,得到数m
- 这个m一定在i之前,因为最末位1变成了0,之前的位不变
- bits[i] = bits[m] + 1,因为末位1变成0,第i个相比较第m个,i的个数少1
代码
func countBits(num int) []int {
bits := make([]int, num+1)
for i := 1; i <= num; i++ {
bits[i] = bits[i&(i-1)] + 1
}
return bits
}