关键词: 动态规划
我对动态规划的理解: 动态规划的关键是找出递推式
方法三最好理解。
题目描述
给定一个非负整数 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)来执行此操作。
前言
这道题需要计算从 0 到 num 的每个数的二进制表示中的 1 的数目。最直观的方法是对每个数直接计算二进制表示中的 1 的数目,时间复杂度较高。也可以使用动态规划的方法,时间复杂度较低。
为了表述简洁,下文用「一比特数」表示二进制表示中的 1 的数目。
方法一:直接计算
关键词:利用位运算
最直观的方法是对从 0 到 num 的每个数直接计算「一比特数」。
每个 int 型的数都可以用 32 位二进制数表示,只要遍历其二进制表示的每一位即可得到 1 的数目。
利用位运算的技巧,可以在一定程度上提升计算速度。按位与运算(&)的一个性质是:对于任意整数 x,令 x=x&(x−1),该运算将 x 的二进制表示的 最后一个 1 变成 0。因此,对 x 重复该操作,直到 x 变成 0,则操作次数即为 x 的「一比特数」。(每一次都将二进制最后一个1变为0,计数器加一,直到所有1都变为0)
class Solution
{
public:
int countOnes(int x)
{
int ones = 0;
while (x > 0)
{
x &= (x - 1);
ones++;
}
return ones;
}
vector<int> countBits(int num)
{
vector<int> bits(num + 1);//0到num有num+1个数
for (int i = 0; i <= num; i++)
{
bits[i]=countOnes(i);
}
return bits;
}
};
c语言
//C
int countOnes(int x)
{
int ones = 0;
while (x > 0)
{
x &= (x - 1);
ones++;
}
return ones;
}
//返回一个数组,所以是int*
int* countBits(int num, int* returnSize)
{
int* bits = malloc(sizeof(int)*(num + 1));
*returnSize = num + 1;//这里不是很理解,*returnSize怎么就代表了长度,returnSize不应该是一个数组吗,*数组名 就是代表数组的大小????
int i = 0;
while (i <= num)
{
bits[i] = countOnes(i);
i++;
}
return bits;
}
错误展示:
不知道为什么注释了*returnSize = num + 1;就会报错,这在编码中也没起到什么作用,不知道后台怎么使用的这个变量。另外这里不是很理解,*returnSize怎么就代表了长度,returnSize不应该是一个数组吗,*数组名 就是代表数组的大小????

复杂度分析
时间复杂度:O(k×num),其中 k 是 int 型的二进制位数,k=32。需要对从 0 到 num 的每个数使用 O(k) 的时间计算「一比特数」,因此时间复杂度是 O(k×num)。
空间复杂度:O(1)。除了返回的数组以外,空间复杂度为常数。
方法二:动态规划——最高有效位
方法一需要对每个数遍历其二进制表示的每一位。可以换一个思路,当计算 i 的「一比特数」时,如果存在 0≤j<i,j 的「一比特数」已知,且 i 和 j 相比,i 的二进制表示只多了一个 1(这里是假设i=y+1),则可以快速得到 i 的「一比特数」。
令 bits[i] 表示 i 的「一比特数」,则上述关系可以表示成:bits[i]=bits[j]+1。
对于正整数 x,如果可以知道最大的正整数 y,使得 y≤x 且 y 是 2 的整数次幂,则 y 的二进制表示中只有最高位是 1,其余都是 0,此时称 y 为 x 的「最高有效位」。令 z=x−y,显然 0≤z<x,则 bits[x]=bits[z]+1。(因为bits[highBit] = 1 , 而bits[i] = bits[i - highBit] + bits[highBit])
判断一个正整数是不是 2 的整数次幂,可以利用方法一中提到的按位与运算的性质。如果正整数 y 是 2 的整数次幂,则 y 的二进制表示中只有最高位是 1,其余都是 0,因此 y&(y−1)=0。由此可见,正整数 y 是 2 的整数次幂,当且仅当 y&(y−1)=0。
显然,0 的「一比特数」为 0。使用 highBit 表示当前的最高有效位,遍历从 1 到 num 的每个正整数 i,进行如下操作。
- 如果 i&(i−1)=0,则令 highBit=i,更新当前的最高有效位。
- i 比 i−highBit 的「一比特数」多 1,由于是从小到大遍历每个数,因此遍历到 i 时,i−highBit 的「一比特数」已知,令 bits[i]=bits[i−highBit]+1。
最终得到的数组 bits 即为答案。
C++:
class Solution
{
public:
vector<int> countBits(int num)
{
vector<int> bits(num + 1);
int highBit = 0;
for (int i = 1; i <= num; i++)
{
if ((i&(i - 1)) == 0)
{
highBit = i;
}
bits[i] = bits[i - highBit] + 1;
}
return bits;
}
};
c语言:
int* countBits(int num, int* returnSize)
{
int* bits = malloc(sizeof(int)*(num + 1));
bits[0]=0;
*returnSize = num + 1;
int hightBit = 0;
for (int i = 1; i <= num; i++)//i从1开始
{
if ((i&(i - 1)) == 0)
{
hightBit = i;
}
bits[i] = bits[i - hightBit] + 1;
}
return bits;
}
复杂度分析
时间复杂度:O(num)。对于每个数,O(1) 的时间计算「一比特数」。
空间复杂度:O(1)。除了返回的数组以外,空间复杂度为常数。
方法三:动态规划——最低有效位
方法二需要实时维护最高有效位,当遍历到的数是 2 的整数次幂时,需要更新最高有效位。如果再换一个思路,可以使用「最低有效位」计算「一比特数」。

遍历从 1 到num 的每个正整数 i,计算 bits 的值。最终得到的数组 bits 即为答案。
C++:
class Solution
{
public:
vector<int> countBits(int num)
{
vector<int> bits(num + 1);
for (int i = 1; i <= num; i++)
{
bits[i] = bits[i >> 1]+(i&1);
}
return bits;
}
};
C语言:
int* countBits(int num, int* returnSize)
{
int* bits = malloc(sizeof(int)*(num + 1));
bits[0] = 0;
*returnSize = num + 1;
for (int i = 0; i <= num; i++)
{
bits[i] = bits[i >> 1] + (i & 1);
}
return bits;
}
复杂度分析
时间复杂度:O(num)。对于每个数,只需要 O(1) 的时间计算「一比特数」。
空间复杂度:O(1)。除了返回的数组以外,空间复杂度为常数。
方法四:动态规划——最低设置位

对于任意整数 x,令 x=x&(x−1),该运算将 x 的二进制表示的最后一个 1 变成 0,令 y=x&(x−1),则 y 为将 x 的最低设置位从 1 变成 0 之后的数,显然0≤y<x,bits[x]=bits[y]+1。因此对任意正整数 x,都有 bits[x]=bits[x&(x−1)]+1。
遍历从 1 到 num 的每个正整数 i,计算 bits 的值。最终得到的数组 bits 即为答案。
C++:
class Solution
{
public:
vector<int> countBits(int num)
{
vector<int> bits(num + 1);//vector默认初始化为0
for (int i = 1; i <= num; i++)//递推式一般不从0开始
{
bits[i] = bits[i&(i - 1)] + 1;
}
return bits;
}
};
C语言:
int* countBits(int num, int* returnSize)
{
int* bits = malloc(sizeof(int)*(num + 1));
bits[0] = 0;
*returnSize = num + 1;
for (int i = 1; i <= num; i++)
{
bits[i] = bits[i&(i - 1)] + 1;
}
return bits;
}
复杂度分析
时间复杂度:O(num)。对于每个数,只需要 O(1) 的时间计算「一比特数」。
空间复杂度:O(1)。除了返回的数组以外,空间复杂度为常数。
97

被折叠的 条评论
为什么被折叠?



