【算法专题训练】03、比特位计数

1、题目信息

cpp 复制代码
给定一个非负整数 n ,请计算 0 到 n 之间的每个数字的二进制表示中 1 的个数,并输出一个数组。
 
示例 :
输入: n = 5
输出: [0,1,1,2,1,2]
解释:0 --> 0
1 --> 1
2 --> 10
3 --> 11
4 --> 100
5 --> 101
  • 审题:输入一个非负整数n,要求从0到n的n+1个数字,每个数字的二进制形式中1个个数,最终结果返回一个包含二进制1的个数的数组。

2、解法1:暴力解法

  • 遍历0到n的数字,计算每个数字的二进制1的个数,并将个数保存到数组中
  • 所以核心是计算数字二进制1的个数,再进一步是计算数字的二进制表示

2.1、求数字的二进制

  • 是数字不断除于2,得到商和余数,如果商不为0继续除2,直到商为0.
  • 将每次相除的余数记录下来,余数的倒序就是对应的二进制形式
  • 最后相除的结果一定是商为0余数为1,所以二进制第一位是1.
cpp 复制代码
例子:求10的二进制,结果为1010
10 / 2 = 5 余 0
5  / 2 = 2 余 1
2  / 2 = 1 余 0
1  / 2 = 0 余 1
把所有余数获取到取反:1010
验证一下1010的十进制结果: 
0*2^0 + 1+2^1 + 0*2^2 + 1*2^3 = 0*1 + 1*2 + 0*4 + 1*8 = 10

2.2、代码实现

  • 只需要将数字n转换成二进制的过程中,记录1的个数,让数字n不断除于2得到商和余数,只要上不为0则继续递归,并使用一个记录记录余数为1的次数
cpp 复制代码
vector<int> countBits(int n)
{
    vector<int> res;
    for (int i = 0; i <= n; i++)
    {
        int count = 0;
        int num = i;
        while (num != 0)
        {
            int remaind = num % 2;
            if (remaind == 1)
            {
                count++;
            }
            num = num / 2;
        }
        res.push_back(count);
    }

    return res;
}

2.3、时间复杂度 O(logn * n)

  • 输入的数字n,需要遍历n+1次,在每次计算数字的二进制时,需要不断除于2,遍历次数为log2,最终遍历结果为logn * n

3、解法2:与运算 i&(i-1)

3.1、思考:数字i的二进制和数字i-1的二进制有什么不同?

  • 数字i的二进制最左边的数字1变为0,1右边如果还有0的话,全部变为1。得到的结果就是i-1的二进制
    举例说明:
  • 10的二进制为1010,则9的二进制为1001
  • 10 & 9 的结果是1000,会将10的二进制最右边的1去除,每次计算1&1-1都会将二进制最右边的1去除,直到所有1都去除,操作的次数也即是二进制1的个数。
    10 & 9 的结果是1000 结果是8
    8 & 7 的结果是0000 结果是0
    操作了两次1&(1-1),说明10的二进制1的个数为2

3.2、代码实现

cpp 复制代码
vector<int> countBits(int n)
{
    vector<int> res;
    for (int i = 0; i <= n; i++)
    {
        int count = 0;
        int num = i;
        while (num != 0)
        {
            num = num & (num - 1);
            count++;
        }
        res.push_back(count);
    }

    return res;
}

3.3、时间复杂度为 O(kn)

  • 需要遍历n+1次,每次遍历获取数字i的二进制1的个数为k

4、解法3:动态规划解法

  • 根据位运算i&(i-1)会将数字i的二进制1的个数减少1,所以有推导公式:
    • i二进制1的个数 = i&(i-1)二进制1的个数 + 1
    • 0的二进制1的个数为0
    • 1的二进制1的个数为:1&0=0的二进制个数+1,等于1
    • 2的二进制1的个数为:2&1=0的二进制个数+1,等于1
    • 3的二进制1的个数为:3&2=1的二进制个数+1,等于2
    • ...

代码实现:

cpp 复制代码
vector<int> countBits(int n)
{
    vector<int> res(n + 1, 0);

    for (int i = 1; i <= n; i++)
    {
        res[i] = res[i & (i - 1)] + 1;
    }

    return res;
}

5、解法4:位移运算
5.1、思考:i的二进制与i/2的二进制的区别?

  • 如果i为偶数,i/2的二进制往左移一位,等于i
    • 例子:假设i为6,二进制为0110,i/2等于3的二进制为0011,3往左移一位等于6
  • 如果i为奇数,则i/2的二进制往左移一位再加1,等于i
    • 例子:i为7,二进制为 0111,i/2的二进制为0011,往左移一位为0110,再加1等于0111
      所以得到推导公式:
      i的二进制个数 = i/2的二进制个数 + i是否为奇数

5.2、代码实现

cpp 复制代码
vector<int> countBits(int n)
{
    vector<int> res(n + 1, 0);

    for (int i = 1; i <= n; i++)
    {
        res[i] = res[i / 2] + (i & 1);
    }

    return res;
}
相关推荐
RaymondZhao34几秒前
【全面推导】策略梯度算法:公式、偏差方差与进化
人工智能·深度学习·算法·机器学习·chatgpt
zhangfeng11339 分钟前
DBSCAN算法详解和参数优化,基于密度的空间聚类算法,特别擅长处理不规则形状的聚类和噪声数据
算法·机器学习·聚类
啊阿狸不会拉杆1 小时前
《算法导论》第 32 章 - 字符串匹配
开发语言·c++·算法
小学生的信奥之路1 小时前
洛谷P3817题解:贪心算法解决糖果分配问题
c++·算法·贪心算法
曙曙学编程2 小时前
stm32——GPIO
c语言·c++·stm32·单片机·嵌入式硬件
你知道网上冲浪吗2 小时前
【原创理论】Stochastic Coupled Dyadic System (SCDS):一个用于两性关系动力学建模的随机耦合系统框架
python·算法·数学建模·数值分析
△曉風殘月〆2 小时前
Visual Studio中的常用调试功能(下)
c++·ide·visual studio·调试
武当豆豆3 小时前
C++编程学习(第25天)
开发语言·c++·学习
地平线开发者4 小时前
征程 6 | PTQ 精度调优辅助代码,总有你用得上的
算法·自动驾驶
Tisfy4 小时前
LeetCode 837.新 21 点:动态规划+滑动窗口
数学·算法·leetcode·动态规划·dp·滑动窗口·概率