题目描述:
给定一个非负整数 num
。对于 0 ≤ i ≤ num
范围中的每个数字 i
,计算其二进制数中的 1
的数目并将它们作为数组返回。
示例 1:
1 | 输入: 2 |
示例 2:
1 | 输入: 5 |
进阶:
- 给出时间复杂度为 $O(n*sizeof(integer))$ 的解答非常容易。但你可以在线性时间 $O(n)$ 内用一趟扫描做到吗?
- 要求算法的空间复杂度为 $O(n)$。
- 你能进一步完善解法吗?要求在 C++ 或任何其他语言中不使用任何内置函数(如 C++ 中的 __builtin_popcount)来执行此操作。
链接:
https://leetcode-cn.com/problems/counting-bits
题目分析
1.动态规划 - 最高位
按照进阶要求中的意思,应该就是不要对每一个数都去模 2 取余一个一个算,那么应该就是要好好利用前面已经算好的结果。观察了一下它们的特点,发现每到 2 的幂次时,就会开启一个新的最高位,并且只有最高位为 1,其他都重置为 0,这个时候前面的那些位又相当于从 0 开始,那么我们就可以取前面的结果。而取前面结果的差值刚好就是这个 2 的幂次。如何判断 2 的幂次呢?它的前一位是全 1,而自己开启了一个新的最高位,并且前面全部置为 0,那么刚好就是没有一位是同 1 的,用“按位与”这种位运算得到的结果是 0。
其实,用位运算判断 2 的幂次这种方法,我是看了题解才了解的。然后又思索了一下,这样的代码下,对于每一个数,都要判断它与它前一位的“按位与”,这样的运算难道不也是 $O(sizeof(integer))$ 的运算吗?这里还是存在着一点不解的。当然,我自己想到的是套一层内层循环,每次循环大小乘 2,也就是直接用我们所知道的“个数”去使它准确。转念想想,每个循环也都需要判断一次是否满足循环条件,而判断循环条件在底层不也是一次位运算吗?并且循环还有自增等运算,在底层也是一次位运算吧。所以应该还是直接套用位运算是最快的,就姑且认为一次位运算是常数时间的咯。
1 | class Solution { |
时间复杂度:$O(n)$,其中 $n$ 就是题目中所给的 n。我们对于每个数的操作是常数时间复杂度的。
空间复杂度:$O(1)$。通常作为答案返回的数组是不计入空间复杂度的,那我们所需的额外空间只有几个变量。
2.动态规划 - 最低位
看了一下官方题解,有 3 种动态规划,这种也是非常好理解的一种。我们也可以从最低位找规律。与前面的思路一样,我们最好能用到前面已经得出的结果,如果我们直接将数字右移一位(也就是除以 2,得到的结果肯定更小),如果最低位是 1,那么就还要加上 1,如果最低位是 0,那 1 的个数是不变的。而获取最低位就是直接将其和 1 进行按位与就可以了。
1 | class Solution { |
时间复杂度:$O(n)$。同上。
空间复杂度:$O(1)$。同上。