1. 程式人生 > 其它 >【位運算】力扣338:位元位計數

【位運算】力扣338:位元位計數

給你一個整數 n ,對於 0 <= i <= n 中的每個 i ,計算其二進位制表示中 1 的個數 ,返回一個長度為 n + 1 的陣列 ans 作為答案。
示例:

輸入:n = 2
輸出:[0,1,1]
解釋:
0 --> 0
1 --> 1
2 --> 10

  1. 暴力:遍歷+統計
    部分程式語言有相應的內建函式用於計算給定的整數的二進位制表示中的 1 的數目。
class Solution:
    def countBits(self, n: int) -> List[int]:
        res = []
        for i in range(n + 1):
            res.append(bin(i).count('1'))
        return res

一行程式碼

class Solution:
    def countBits(self, n: int) -> List[int]:
        return  [bin(i).count('1') for i in range(n+1)]

作者:Jam007
連結:https://leetcode.cn/problems/counting-bits/solution/yi-xing-dai-ma-si-lu-jian-dan-xing-neng-sctro/

時間複雜度: O(N * sizeof(int))
空間複雜度:O(1),返回陣列不計入空間複雜度中

  1. Brian Kernighan 演算法
    最直觀的做法是對從 0 到 n 的每個整數直接計算「一位元數」。每個 int 型的數都可以用 32 位二進位制數表示,只要遍歷其二進位制表示的每一位即可得到 1 的數目。
    利用 Brian Kernighan 演算法,可以在一定程度上進一步提升計算速度。Brian Kernighan 演算法的原理是:對於任意整數 x,令 x = x & (x−1),該運算將 x 的二進位制表示的最後一個 1 變成 0。因此,對 x 重複該操作,直到 x 變成 0,則操作次數即為 x 的「一位元數」。
class Solution:
    def countBits(self, n: int) -> List[int]:
        def countOnes(x: int) -> int:
            ones = 0
            while x > 0:
                x &= (x - 1)
                ones += 1
            return ones

        bits = [countOnes(i) for i in range(n + 1)] # 陣列哦
        return bits

作者:LeetCode-Solution
連結:https://leetcode.cn/problems/counting-bits/solution/bi-te-wei-ji-shu-by-leetcode-solution-0t1i/

時間複雜度:O(nlogn)。需要對從 0 到 n 的每個整數使用計算「一位元數」,對於每個整數計算「一位元數」的時間都不會超過 O(logn)。
空間複雜度:O(1)。除了返回的陣列以外,空間複雜度為常數。

直接從結果分析
把第 i 個數分成兩種情況:

  • i 是偶數,那麼它的二進位制 1 的位數與 i/2 的二進位制 1 的位數相等;因為偶數的二進位制末尾是 0,右移一位等於 i/2,而二進位制中 1 的個數沒有變化。
  • i 是奇數,那麼【i 的二進位制 1 的位數 = i - 1 的二進位制位數 + 1】;因為奇數的二進位制末尾是 1,如果把末尾的 1 去掉就等於 i - 1。又 i - 1 是偶數,所以奇數 i 的二進位制 1 的個數等於 i/2 中二進位制 1 的位數 +1。

因此可以從遞迴方面考慮,從而想到記憶化搜尋,從而動態規劃。

  1. 遞迴
class Solution(object):
    def countBits(self, n: int) -> List[int]:
        res = []
        for i in range(n + 1):
            res.append(self.count(i))
        return res

    def count(self, n):
        if n == 0:
            return 0
        if n % 2 == 1: # 奇數
            return self.count(n - 1) + 1
        return self.count(n // 2)

作者:fuxuemingzhu
連結:https://leetcode.cn/problems/counting-bits/solution/yi-bu-bu-fen-xi-tui-dao-chu-dong-tai-gui-3yog/

時間複雜度: O(N ^ 2),因為遍歷了一次,每次求解最多需要遞迴 N/2次。
空間複雜度:O(N),遞迴需要呼叫系統棧,棧的大小最多為 N/2。

  1. 記憶化搜尋
    在遞迴解法中,其實有很多重複的計算,比如當 i = 8 的時候,需要求 i = 4, 2, 1, 0 的情況,而這些取值已經計算過了,此時可以使用記憶化搜尋。
    所謂記憶化搜尋,就是在每次遞迴函式結束的時候,把計算結果儲存起來。這樣的話,如果下次遞迴的時候遇到了同樣的輸入,則直接從儲存的結果中直接查詢並返回,不用再次遞迴
    舉個例子,比如 i = 8 的時候,需要求 i = 4 的情況,而 i = 4 的情況在之前已經計算過了,因此直接返回 memo[4] 即可。
class Solution(object):
    def countBits(self, n: int) -> List[int]:
        self.memo = [0] * (n + 1)
        res = []
        for i in range(n + 1):
            res.append(self.count(i))
        return res

    def count(self, n):
        if n == 0:
            return 0
        if self.memo[n] != 0:
            return self.memo[n]
        if n % 2 == 1:
            res = self.count(n - 1) + 1
        else:
            res = self.count(n // 2)
        self.memo[n] = res # 能看出此題可以不用 memo 陣列,直接利用 res 儲存結果
        return res

作者:fuxuemingzhu
連結:https://leetcode.cn/problems/counting-bits/solution/yi-bu-bu-fen-xi-tui-dao-chu-dong-tai-gui-3yog/

時間複雜度:O(N),因為遍歷了一次,每次求解都可以從之前的記憶化結果中找到。
空間複雜度:O(N),用到了輔助的空間儲存結果,空間的結果是 O(N)。

  1. 動態規劃
    很多時候,動態規劃的方法都是從記憶化搜尋中優化出來的。本題也可以如此。
    在記憶化搜尋過程中,我們看到其實每次呼叫遞迴函式的時候,遞迴函式只會執行一次,就被 memo 捕獲並返回了。那麼其實可以去除遞迴函式,直接從 res 陣列中查結果。
    此外,運用位運算的思想優化:
  • 對於第 i 個數字,如果它二進位制的最後一位(最低位)為 1,那麼它含有 1 的個數則為 dp[i-1] + 1;
  • 如果它二進位制的最後一位(最低位)為 0,那麼它含有 1 的個數和其算術右移結果相同,即 dp[i>>1]。
class Solution:
    def countBits(self, n: int) -> List[int]:
        dp = [0] * (n + 1)
        for i in range(1, n + 1):
            if i & 1: # 最低位 i & 1 == 1
                dp[i] = dp[i - 1] + 1
            else:
                dp[i] = dp[i >> 1]
        return dp

簡單一點就是:
i 的二進位制中 1 的個數 = (i 右移一位後 1 的個數) + (i 的最低位)

class Solution:
    def countBits(self, n: int) -> List[int]:
        dp = [0] * (n + 1)
        for i in range(1, n + 1):
            dp[i] = dp[i >> 1] + (i & 1)
        return dp

時間複雜度:O(N),因為遍歷了一次。
空間複雜度:O(1),返回結果佔用的空間不計入空間複雜度中。

  1. 規律技巧型
    列出0-8的二進位制數:
    0 0
    1 1
    2 10
    3 11
    4 100
    5 101
    6 110
    7 111
    8 1000
    觀察可以發現一個規律:例如3和7,7比3大4,體現在二進位制中,111就是在11的首位添上1。自然1的個數也要加一。
    那這個規律就是,任何一個大於等於1的數字n,它的二進位制數中一的個數實際就是之前某個數n-k的一的個數+1。
    k很顯然就是不大於n的2的整數次冪中的最大值。
    在進行遍歷的時候只需要額外維護一個k值,每當n>=k,k更新為k*2。
class Solution:
    def countBits(self, num: int) -> List[int]:
        dp = [0] * (num + 1)
        k = 1
        for i in range(1,num+1):
            if i >= k * 2:
                k = k * 2
            dp[i] = dp[i-k]+1
        return dp

作者:eager-6oldstine
連結:https://leetcode.cn/problems/counting-bits/solution/ling-lei-de-dong-tai-gui-hua-si-lu-by-ea-tm64/