题目描述:
给定一个非负整数 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)来执行此操作。
方法一:
class Solution {
public:
vector<int> countBits(int n) {
vector<int> ans;
int count;
int temp;
for (int i = 0; i <= n; i++)
{
count = 0;
temp = i;
while (temp != 0)
{
temp &= (temp - 1);
count++;
}
ans.push_back(count);
}
return ans;
}
};
这道题实在是没思路,方法一是官方题解中的Brian Kernighan 算法,原理为:
对于任意整数 xx,令 x=x & (x−1),该运算将 x 的二进制表示的最后一个 1 变成 0。因此,对 x 重复该操作,直到 x 变成 0,则操作次数即为 x 的「一比特数」。
时间复杂度:O(nlogn)。需要对从 0 到 n 的每个整数使用计算「一比特数」,对于每个整数计算「一比特数」的时间都不会超过 O(logn)。
空间复杂度:O(1)。除了返回的数组以外,空间复杂度为常数。
方法二:
class Solution {
public:
vector<int> countBits(int n) {
vector<int> bits(n+1);
int highbit = 0;
for (int i = 1; i <= n; i++)
{
if ((i & (i - 1)) == 0)
//更新最高有效位,由于&运算优先级低于==,所以一定要加个括号
{
highbit = i;
}
bits[i] = bits[i - highbit] + 1;
}
return bits;
}
};
关于最高有效位的动态规划法:
时间复杂度:O(n)。对于每个整数,只需要 O(1) 的时间计算「一比特数」。
空间复杂度:O(1)。除了返回的数组以外,空间复杂度为常数。
实在是太精彩了!根本想不到这样的方法,虽然跟我对位运算的不熟悉有关系,但自己还是无论怎样都想不到动态规划的路子上去,该方法虽然读起来有点拗口,但搞懂了之后就知道也并不复杂,举个例子:当遍历到4(二进制为100),最高有效位highbit更新为4;遍历到5(二进制为101),5的「一比特数」即为5(二进制为101)- 4(二进制为100)= 1(二进制为001)的「一比特数」加1。
类似的,11的「一比特数」即为11(二进制为1011)- 8(二进制为1000)= 3(二进制为011)的「一比特数」加1。
方法三:
class Solution {
public:
vector<int> countBits(int n) {
vector<int> bits(n+1);
for (int i = 1; i <= n; i++)
{
bits[i] = bits[i >> 1] + (i & 1);
}
return bits;
}
};
关于最低有效位的动态规划:
时间复杂度:O(n)。对于每个整数,只需要 O(1) 的时间计算「一比特数」。
空间复杂度:O(1)。除了返回的数组以外,空间复杂度为常数。
我觉得这个方法同样精彩,而且比方法二更好懂些,上图中所提到的 x&1 其实是利用与运算来判断某数是否为偶数的方法:只要根据最未位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if ((a & 1) == 0)代替if (a % 2 == 0)来判断a是不是偶数。所以用x&1可以得到x除以2的余数。
方法四:
class Solution {
public:
vector<int> countBits(int n) {
vector<int> bits(n+1);
for (int i = 1; i <= n; i++)
{
bits[i] = bits[i & (i-1)] + 1;
}
return bits;
}
};
关于最低设置位的动态规划:
时间复杂度:O(n)。对于每个整数,只需要 O(1) 的时间计算「一比特数」。
空间复杂度:O(1)。除了返回的数组以外,空间复杂度为常数。
从方法一开始看下来到方法四,已经能够瞬间读懂题解,理解其做法了,动态规划种真是复杂却又简单的方法,在开始思考时绞尽脑汁也想不到正确的道路上去,而在理清了思路,归纳出正确的状态转移方程之后,书写代码又是那样的轻松和简洁,还是要继续努力!