Loading

Medium | LeetCode 338. 比特位计数 | 动态规划 + 位运算

338. 比特位计数

给定一个非负整数 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)来执行此操作。

方法一:对每个数字计算Pop Count(汉明权重)

计算一个数字的二进制位数的方法如 Easy | LeetCode 191 | 剑指 Offer 15. 二进制中1的个数 | 位运算 所示。通过 n & (n - 1)可以把最右边的1变成0, 通过计算 n&(n-1) 操作的次数即可得到二进制中1的个数。

public int[] countBits(int num) {
    int[] res = new int[num+1];
    for (int i = 0; i <= num; i++) {
        res[i] = popCount(i);
    }
    return res;
}

private int popCount(int num) {
    int res = 0;
    while (num != 0) {
        res++;
        num &= (num - 1);
    }
    return res;
}

方法二: 动态规划

如方法一可以知道 数字 n 的二进制里1的位数, 比 n&(n-1) 的二进制位数 多1, 并且 n > n &(n-1), 则可以得到动态规划转移方程。

\[P(x)=P(x \&(x-1))+1 ; \]

利用此动态转移方程, 可以得到如下的代码

public int[] countBits(int num) {
    int[] ans = new int[num + 1];
    for (int i = 1; i <= num; ++i)
      ans[i] = ans[i & (i - 1)] + 1;
    return ans;
}

方法三:

已经某个数n的二进制1的位数, 通过将n的 高位每一位尝试设置为1 的方式, 可以得到下面的转移方程。

\[P(x+b)=P(x)+1, b=2^{m}>x \]

依据此状态转移方程, 可以得到如下的代码:

public int[] countBits(int num) {
    int[] ans = new int[num + 1];
    int i = 0, b = 1;
    // [0, b) is calculated
    while (b <= num) {
        // generate [b, 2b) or [b, num) from [0, b)
        while(i < b && i + b <= num){
            ans[i + b] = ans[i] + 1;
            ++i;
        }
        i = 0;   // reset i
        b <<= 1; // b = 2b
    }
    return ans;
}

方法四:

通过将n最低位(2^0位)的数字抹掉(抹掉之后, 其二进制个数与n>>1相同), 可以得到如下的状态转移方程。

\[P(x)=P(x / 2)+(x \quad \bmod 2) \]

public int[] countBits(int num) {
    int[] ans = new int[num + 1];
    for (int i = 1; i <= num; ++i) {
        ans[i] = ans[i >> 1] + (i & 1);
    }
    // x / 2 is x >> 1 and x % 2 is x & 1
    return ans;
}
posted @ 2021-01-25 22:17  反身而诚、  阅读(66)  评论(0编辑  收藏  举报