【位运算-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)
相关推荐
飞Link14 分钟前
什么是 PX4?一文了解 PX4 在空地协同中的作用
算法·无人机
进化中的刀盾兽36 分钟前
从零实现 C++ Json-Rpc(十):把消息交给真实连接——MuduoConnection 与 ConnectionFactory
c++·rpc
一去不复返的通信er7 小时前
力扣-图论-岛屿数量
算法·leetcode·图论
青瓦梦滋7 小时前
WebSocket协议
linux·服务器·网络·c++·websocket·网络协议·http
程序 代码狂人10 小时前
切片表与分区表的区别(数据开发面试版)
java·大数据·开发语言·数据仓库·算法
HDU-Dade10 小时前
【校招笔试真题】简要介绍 Self-Attention 的前沿算法
人工智能·算法
Xin77011 小时前
LeetCode 79. 单词搜索(DFS 回溯法)
leetcode
小郎君65812 小时前
Vue keep-alive 切标签后滚动位置回顶?因为 onDeactivated 里读到的 scrollTop 是 0
算法
CodeYp201913 小时前
第15讲:二叉搜索树
开发语言·c++·二叉搜索树
邵奈一13 小时前
GPT-6.1 Terra速度是Sol的3倍,价格减半
算法