问题分析
1. 这题看上去是一道比较有意思的数论题,应该是要用到位运算,所以试着做了一下。
2. 给出一个整数num,要求统计0到这个整数num的二进制写法中1的个数。最容易的做法就是转换为二进制一位一位统计,这是题目中描述的O(n*sizeof(integer))的时间复杂度,又或者考虑将0到MAX_INT的1的个数前缀和存起来直接打表输出。但题目要求时间和空间都要是O(n)的级别,这两种方案显然都是不能通过的。
3. 尝试用找规律的方法做,考虑将[0, 2^(k+1)]划分为一个区间。
第一个区间 [0, 1]
第二个区间 [00, 01, 10, 11]
第三个区间 [000, 001, 010, 011, 100, 101, 110, 111]
...
也就是后一个区间是在前面全部区间的基础上全部加上前缀1,可以由此得到递推公式
ak 是从[0, 2^k - 1]中的1的个数
a1 = 1
a2 = a1 * 2 + 2 ^ 1
...
an = an-1 * 2 + 2 ^ (n - 1)
依据这个递推公式和规律可以很容易地算出num中二进制中1的个数。
4. 再考虑其实这个递推公式可以转化为更简单的形式
即如果k是奇数,则k中1的数量等于k / 2的1的数量加一,对应的是增加后缀1
如果k是偶数,则k中1的数量等于k / 2的1的数量,对应的是增加后缀0
5. 最后一个方法是我在网上看到的很漂亮的写法
指出 i 和 i & (i - 1)之间1的数量恰好差1,可以利用这个关系来求解
但是让我好奇的是为什么这个关系是成立的
考虑一个二进制串 [a b c d]
假设d为1,则i - 1为 [a b c 0]
i & (i - 1) = [a b c 0] 恰好差一个1
假设d为0,则i - 1为 [a b -c 1] (此时c为1)或 [a -b -c 1] (此时c为0 b为1)或 [-a -b -c 1] (此时c为0 b为0 a为1)
也就是i比(i - 1)恰好多从右边开始数起的第一个1
仔细想利用这个方法似乎可以快速判断右边开始的第一个1是在哪里,或者某个数是否是2的整数次幂。
题目代码
class Solution {
public:
vector<int> countBits(int num) {
vector<int> res(num + 1, 0);
for (int i = 1; i <= num; ++i) {
res[i] = res[i & (i - 1)] + 1;
}
return res;
}
};