2021.8.23 力扣-比特位计数

本文解析四种高效算法解决题目:计算非负整数 num 的二进制表示中1的数目。方法包括Brian Kernighan算法、最高/最低有效位动态规划,以及利用位运算技巧。通过实例深入理解并实现线性时间复杂度O(n)的解决方案。
摘要由CSDN通过智能技术生成

题目描述:

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

方法一

class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> ans;
        int count;
        int temp;
        for (int i = 0; i <= n; i++)
        {
            count = 0;
            temp = i;
            while (temp != 0)
            {
                temp &= (temp - 1);
                count++;
            }
            ans.push_back(count);
        }
        return ans;
    }
};

这道题实在是没思路,方法一是官方题解中的Brian Kernighan 算法,原理为:

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

时间复杂度:O(nlogn)。需要对从 0 到 n 的每个整数使用计算「一比特数」,对于每个整数计算「一比特数」的时间都不会超过 O(logn)。

空间复杂度:O(1)。除了返回的数组以外,空间复杂度为常数。

方法二

class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> bits(n+1);
        int highbit = 0;
        for (int i = 1; i <= n; i++)
        {
            if ((i & (i - 1)) == 0)
            //更新最高有效位,由于&运算优先级低于==,所以一定要加个括号
            {
                highbit = i;
            }
            bits[i] = bits[i - highbit] + 1;
        }
        return bits;
    }
};

关于最高有效位的动态规划法:

时间复杂度:O(n)。对于每个整数,只需要 O(1) 的时间计算「一比特数」。

空间复杂度:O(1)。除了返回的数组以外,空间复杂度为常数。

实在是太精彩了!根本想不到这样的方法,虽然跟我对位运算的不熟悉有关系,但自己还是无论怎样都想不到动态规划的路子上去,该方法虽然读起来有点拗口,但搞懂了之后就知道也并不复杂,举个例子:当遍历到4(二进制为100),最高有效位highbit更新为4;遍历到5(二进制为101),5的「一比特数」即为5(二进制为101)- 4(二进制为100)= 1(二进制为001)的「一比特数」加1。

类似的,11的「一比特数」即为11(二进制为1011)- 8(二进制为1000)= 3(二进制为011)的「一比特数」加1。

 方法三

class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> bits(n+1);
        for (int i = 1; i <= n; i++)
        {
            bits[i] = bits[i >> 1] + (i & 1);
        }
        return bits;
    }
};

 关于最低有效位的动态规划:

时间复杂度:O(n)。对于每个整数,只需要 O(1) 的时间计算「一比特数」。

空间复杂度:O(1)。除了返回的数组以外,空间复杂度为常数。

我觉得这个方法同样精彩,而且比方法二更好懂些,上图中所提到的 x&1 其实是利用与运算来判断某数是否为偶数的方法:只要根据最未位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if ((a & 1) == 0)代替if (a % 2 == 0)来判断a是不是偶数。所以用x&1可以得到x除以2的余数。

方法四:

class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> bits(n+1);
        for (int i = 1; i <= n; i++)
        {
            bits[i] = bits[i & (i-1)] + 1;
        }
        return bits;
    }
};

关于最低设置位的动态规划:

 

时间复杂度:O(n)。对于每个整数,只需要 O(1) 的时间计算「一比特数」。

空间复杂度:O(1)。除了返回的数组以外,空间复杂度为常数。

从方法一开始看下来到方法四,已经能够瞬间读懂题解,理解其做法了,动态规划种真是复杂却又简单的方法,在开始思考时绞尽脑汁也想不到正确的道路上去,而在理清了思路,归纳出正确的状态转移方程之后,书写代码又是那样的轻松和简洁,还是要继续努力!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值