【位运算-1】338.比特位计数

题目描述:

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

不要使用内置函数来解决(例如,C++ 中的 __builtin_popcount)。

示例 1:

复制代码
输入:n = 2
输出:[0,1,1]
解释:
0 --> 0
1 --> 1
2 --> 10

示例 2:

复制代码
输入:n = 5
输出:[0,1,1,2,1,2]
解释:
0 --> 0
1 --> 1
2 --> 10
3 --> 11
4 --> 100
5 --> 101

进阶:

  • 很容易就能实现时间复杂度为 O(n log n) 的解决方案,你可以在线性时间复杂度 O(n) 内用一趟扫描解决此问题吗?

解题思路:

方法一:动态规划(最低有效位)

核心思路:

对于任意整数 i:

复制代码
i 的二进制中 1 的个数 = (i >> 1) 的二进制中 1 的个数 + (i & 1)

因为 i >> 1 是 i 去掉最低位,i & 1 是最低位是否为 1。

代码实现:

cpp 复制代码
class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> dp(n + 1, 0);
        for (int i = 1; i <= n; i++) {
            dp[i] = dp[i >> 1] + (i & 1);
        }
        return dp;
    }
};

具体过程示例:

n = 5

i 二进制 i>>1 i&1 dpi
0 0 - - 0
1 1 0 1 dp0+1=1
2 10 1 0 dp1+0=1
3 11 1 1 dp1+1=2
4 100 2 0 dp2+0=1
5 101 2 1 dp2+1=2

结果:[0,1,1,2,1,2] ✅

复杂度分析:

维度 复杂度 说明
时间复杂度 O(n) 一次遍历
空间复杂度 O(n) dp 数组(不算输出的话是 O(1))

方法二:动态规划(最高有效位)

核心思路:

找到小于等于 i 的最大 2 的幂 highBit:

复制代码
dp[i] = dp[i - highBit] + 1

因为 i 比 i - highBit 多了一个最高位的 1。

代码实现:

cpp 复制代码
class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> dp(n + 1, 0);
        int highBit = 0;
        
        for (int i = 1; i <= n; i++) {
            if ((i & (i - 1)) == 0) {
                highBit = i;  // i 是 2 的幂
            }
            dp[i] = dp[i - highBit] + 1;
        }
        
        return dp;
    }
};

具体过程示例:

n = 5

i highBit dpi
0 - 0
1 1 dp0+1=1
2 2 dp0+1=1
3 2 dp1+1=2
4 4 dp0+1=1
5 4 dp1+1=2

结果:[0,1,1,2,1,2] ✅

复杂度分析:

维度 复杂度 说明
时间复杂度 O(n) 一次遍历
空间复杂度 O(n) dp 数组

方法三:动态规划(最低设置位)

核心思路:

复制代码
dp[i] = dp[i & (i - 1)] + 1

因为 i & (i - 1) 是 i 去掉最低位的 1,所以 dp[i] 比它多 1。

代码实现:

cpp 复制代码
class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> dp(n + 1, 0);
        for (int i = 1; i <= n; i++) {
            dp[i] = dp[i & (i - 1)] + 1;
        }
        return dp;
    }
};

具体过程示例:

n = 5

i i&(i-1) dpi
0 - 0
1 0 dp0+1=1
2 0 dp0+1=1
3 2 dp2+1=2
4 0 dp0+1=1
5 4 dp4+1=2

结果:[0,1,1,2,1,2] ✅

复杂度分析:

维度 复杂度 说明
时间复杂度 O(n) 一次遍历
空间复杂度 O(n) dp 数组

方法四:暴力法(O(n log n))

核心思路:

对 0 到 n 中的每一个数 ,单独统计它二进制表示中 1 的个数。

统计单个数的 1 的个数:

对于一个整数 num:

  1. 看它的最低位是不是 1:num & 1

  2. 把 num 右移一位:num >>= 1

  3. 重复上述过程,直到 num == 0

例子 :num = 5(二进制 101)

步骤 num num & 1 count
1 101 1 1
2 10 0 1
3 1 1 2
4 0 - 结束

结果:count = 2

代码实现:

cpp 复制代码
class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> result(n + 1, 0);
        for (int i = 1; i <= n; i++) {
            int count = 0;
            int num = i;
            while (num > 0) {
                count += num & 1;
                num >>= 1;
            }
            result[i] = count;
        }
        return result;
    }
};

具体过程示例:

n = 5

i 二进制 统计过程 count
0 0 - 0
1 1 1&1=1 → 0 1
2 10 10&1=0 → 1&1=1 → 0 1
3 11 11&1=1 → 1&1=1 → 0 2
4 100 100&1=0 → 10&1=0 → 1&1=1 → 0 1
5 101 101&1=1 → 10&1=0 → 1&1=1 → 0 2

结果:[0,1,1,2,1,2] ✅

复杂度分析:

维度 复杂度 说明
时间复杂度 O(n log n) 每个数最多遍历 log n 位
空间复杂度 O(n) 结果数组

缺点:不是最优,但容易理解。

四种方法对比:

方法 时间复杂度 空间复杂度 推荐度
最低有效位 O(n) O(n) ⭐⭐⭐⭐⭐
最高有效位 O(n) O(n) ⭐⭐⭐⭐
最低设置位 O(n) O(n) ⭐⭐⭐⭐⭐
暴力法 O(n log n) O(n) ⭐⭐

总结:

要点 说明
核心思想 利用 i 和 i>>1 或 i&(i-1) 的关系递推
最优解法 最低有效位 / 最低设置位,时间 O(n)
关键公式 dp[i] = dp[i>>1] + (i&1)
相关推荐
小师兄吃牛肉2 小时前
C语言中的nullptr有必要吗
c语言·c++·算法
WL学习笔记2 小时前
日期类(C++)
开发语言·c++·日期类实现
H.莓飛3 小时前
【C++】命名空间、缺省参数、函数重载与引用
linux·开发语言·c++·visual studio
一只小小的芙厨3 小时前
【线性DP】
学习·算法·动态规划
xxwl5853 小时前
数据结构知识点和代码实现总结(C语言实现)
c语言·开发语言·数据结构
奋发向前wcx4 小时前
CSP-J复赛模拟赛5 王晨旭补题 2026.10.5
算法
汉克老师4 小时前
GESP2026年9月认证C++八级( 第一部分选择题(1~7题)精讲
c++·gesp·小学生·学c++编程
罗湖老棍子5 小时前
C Looooops(信息学奥赛一本通- P1640)
算法·数论·同余·裴蜀定理·扩展欧几里得
郝学胜_神的一滴6 小时前
C++ Templates 06:搞懂模板代码的三种组织方式
c++·visual studio