力扣刷题笔记: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)来执行此操作。

方法一:暴力法

题解思路:
这个应该是最容易想到的方法,遍历从 0 到 num 的每个数字,统计每个数字的二进制中 1 的个数。

复杂度:
时间复杂度: O(N * sizeof(int))
空间复杂度:O(1),返回数组不计入空间复杂度中。

题解python代码:

class Solution:
    def countBits(self, num: int) -> List[int]:
        res = []
        for i in range(num + 1):
            res.append(bin(i).count("1"))
        return res

方法二:记忆搜索法

题解思路:
在上面暴力解法中,其实有很多重复的计算,比如当 i = 8 的时候,需要求 i = 4, 2, 1, 0 的情况,而这些取值已经计算过了,此时可以使用记忆化搜索。

所谓记忆化搜索,就是在每次递归函数结束的时候,把计算结果保存起来。这样的话,如果下次递归的时候遇到了同样的输入,则直接从保存的结果中直接查询并返回,不用再次重复计算。

举个例子,比如 i = 8 的时候,需要求 i = 4 的情况,而 i = 4 的情况在之前已经计算过了,因此直接返回 memo[4] 即可。

复杂度:
时间复杂度: O(N),因为遍历了一次,每次求解都可以从之前的记忆化结果中找到。
空间复杂度:O(N),用到了辅助的空间保存结果,空间的结果是 O(N)。

题解python代码:

class Solution(object):
    def countBits(self, num):
        self.memo = [0] * (num + 1)
        res = []
        for i in range(num + 1):
            res.append(self.count(i))
        return res
    
    def count(self, num):
        if num == 0:
            return 0
        if self.memo[num] != 0:
            return self.memo[num]
        if num % 2 == 1:
            res = self.count(num - 1) + 1
        else:
            res = self.count(num // 2)
        self.memo[num] = res
        return res

方法三:动态规划

其实很多时候,动态规划的方法都是从记忆化搜索中优化出来的。本题也可以如此。

方法二在记忆化搜索过程中,我们看到其实每次调用递归函数的时候,递归函数只会运行一次,就被 memo 捕获并返回了。那么其实可以去除递归函数,直接从 res 数组中查结果。

同时,优化了一下转移方程的表达式为 answer[i] = answer[i >> 1] + (i & 1) 。

于是得到下面的动态规划的方案。

复杂度:
时间复杂度: O(N)O(N),因为遍历了一次。
空间复杂度:O(1)O(1),返回结果占用的空间不计入空间复杂度中。

题解python代码:

class Solution:
    def countBits(self, num):
        res = [0] * (num + 1)
        for i in range(1, num + 1):
            res[i] = res[i >> 1] + (i & 1)
        return res

作者:fuxuemingzhu
链接:https://leetcode-cn.com/problems/counting-bits/solution/yi-bu-bu-fen-xi-tui-dao-chu-dong-tai-gui-3yog/
来源:力扣(LeetCode)https://leetcode-cn.com/problems/counting-bits/

方法四:位运算(强烈推荐)

题解思路:
可以换一个思路,当计算 i 的「一比特数」时,如果存在 0≤j

令 bits[i] 表示 ii 的「一比特数」,则上述关系可以表示成:[j]+1bits[i]=bits[j]+1。

对于正整数 x,如果可以知道最大的正整数 y,使得 y≤x 且 y 是 2 的整数次幂,则 y 的二进制表示中只有最高位是 1,其余都是 0,此时称 y 为 x 的「最高有效位」。令 z=x−y,显然 0≤z

为了判断一个正整数是不是 2 的整数次幂,可以利用方法一中提到的按位与运算的性质。如果正整数 y 是 2 的整数次幂,则 y 的二进制表示中只有最高位是 1,其余都是 0,因此 y&(y−1)=0。由此可见,正整数 y 是 2 的整数次幂,当且仅当 y&(y−1)=0。

显然,0 的「一比特数」为 0。使用 highBit 表示当前的最高有效位,遍历从 11 到 num 的每个正整数 ii,进行如下操作。

如果 i&(i−1)=0,则令 highBit=i,更新当前的最高有效位。

i 比 i−highBit 的「一比特数」多 11,由于是从小到大遍历每个数,因此遍历到 i 时,i−highBit 的「一比特数」已知,令 bits[i]=bits[i−highBit]+1。

最终得到的数组 bits 即为答案。

题解python代码:

class Solution:
    def countBits(self, num: int) -> List[int]:
        bits = [0]
        highBit = 0
        for i in range(1, num + 1):
            if i & (i - 1) == 0:
                highBit = i
            bits.append(bits[i - highBit] + 1)
        return bits

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/counting-bits/solution/bi-te-wei-ji-shu-by-leetcode-solution-0t1i/
来源:力扣(LeetCode)https://leetcode-cn.com/problems/counting-bits/

你可能感兴趣的:(刷题笔记,leetcode,python)