leetcode:338. 比特位计数

题目

给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。

示例:

  1. 输入:n = 2
  2. 输出:[0,1,1]
  3. 解释:
  4. 0 --> 0
  5. 1 --> 1
  6. 2 --> 10
  1. 输入:n = 5
  2. 输出:[0,1,1,2,1,2]
  3. 解释:
  4. 0 --> 0
  5. 1 --> 1
  6. 2 --> 10
  7. 3 --> 11
  8. 4 --> 100
  9. 5 --> 101

进阶

  • 很容易就能实现时间复杂度为 O(n log n) 的解决方案,你可以在线性时间复杂度 **O(n)** 内用一趟扫描解决此问题吗?
  • 你能不使用任何内置函数解决此问题吗?(如,C++ 中的 __builtin_popcount

解答 & 代码

解法一:x = x & (x-1)

Brian Kernighan 算法:对于任意整数 x,令 x = x & (x-1),该运算将 x 的二进制表示的最后一个 1 变成 0。因此,对 x 重复该操作,直到 x 变成 0,则操作次数即为 x 的「一比特数」。

遍历 0~n,对每个 n 利用 Brian Kernighan 算法求一比特数即可

  1. class Solution {
  2. public:
  3. vector<int> countBits(int n) {
  4. vector<int> result(n + 1);
  5. for(int i = 0; i <= n; ++i)
  6. {
  7. int num = i; // 当前整数
  8. int cnt = 0; // 一比特数
  9. // 令 num = num & (num - 1),直到 num 变为 0,该操作的次数就是 num 的一比特数
  10. while(num != 0)
  11. {
  12. ++cnt;
  13. num &= num - 1;
  14. }
  15. result[i] = cnt;
  16. }
  17. return result;
  18. }
  19. };

复杂度分析:

  • 时间复杂度 O(n logn):对于给定的 n,计算从 0 到 n 的每个整数的「一比特数」的时间都不会超过 O(logn),因此总时间复杂度为 O(nlogn)
    • 对于整数 n, 其二进制表示的比特位数 = [简单] 338. 比特位计数 - 图1+ 1,最坏情况下,每一位都是 1,就需要循环计算 [简单] 338. 比特位计数 - 图2+ 1 次,因此计算其一比特数的时间复杂度 O(logn)
  • 空间复杂度 O(1):结果数组 result 不计

执行结果:

  1. 执行结果:通过
  2. 执行用时:4 ms, 在所有 C++ 提交中击败了 84.77% 的用户
  3. 内存消耗:7.6 MB, 在所有 C++ 提交中击败了 81.67% 的用户

解法二:动态规划 + x & (x-1)

动态规划

  • 动态规划数组 **result**result[i] 代表整数 i 的一比特数(二进制表示中 1 的个数)
  • 状态转移方程result[i] = 1 + result[i & i - 1]
    • j = i & (i - 1),整数 j 是将整数 i 的二进制表示的最后一个 1 变成 0,因此 j 肯定小于 ij 的一比特数 result[i] 已经计算得到,因此 result[i] = 1 + result[j]
  • 初始化result[0] = 0

    1. class Solution {
    2. public:
    3. vector<int> countBits(int n) {
    4. vector<int> result(n + 1); // 动态规划数组,result[i] 代表整数 i 的一比特数
    5. result[0] = 0; // 初始化
    6. // 状态转移
    7. for(int i = 1; i <= n; ++i)
    8. result[i] = 1 + result[i & i - 1];
    9. return result;
    10. }
    11. };

    复杂度分析:

  • 时间复杂度 O(n):一趟遍历即可,对于每个整数,只需 O(1) 的时间计算一比特数

  • 空间复杂度 O(1):结果数组 result 不计

执行结果:

  1. 执行结果:通过
  2. 执行用时:0 ms, 在所有 C++ 提交中击败了 100.00% 的用户
  3. 内存消耗:7.8 MB, 在所有 C++ 提交中击败了 35.97% 的用户