類型題Ⅳ:前綴和

前綴和

前綴和 就是數組 第 0 項當前項 的總和。比如數組 nums,那麼它的前綴和 prefixSum[x] 就表示 nums 從第 0 項到第 x 項的總和。
prefixSum[x]=nums[0]+nums[1]+...+nums[x] prefixSum[x] = nums[0] + nums[1] + ...+ nums[x]
數組 nums 中任意一項就是兩個相鄰前綴和之差
nums[x]=prefixSum[x]prefixSum[x1] nums[x] = prefixSum[x] - prefixSum[x-1]
數組 nums 中第 i 項到第 j 項之和:
num[i]+...+num[j]=prefixSum[j]prefixSum[i1] num[i] + ... + num[j] = prefixSum[j] - prefixSum[i-1]
特別注意,由於 i 可以爲 0,i-1 會出現數組越界,所以前綴和數組一般需要在 0 位置擴充一個 0,便於計算數組 nums 中第 0 項到第 k 項之和。

前綴和數組每一項 dp[i] 都是原數組從第 0 項到第 i 項的總和,所以如果 j 大於 i,那麼 dp[j] 就一定是包含 dp[i] 的。

相關題目

560. 和爲K的子數組

思路一暴力解法,雙重循環,每次固定一個 i,用 j 循環往前走,每到一個位置判斷子數組和是否爲 target,是則 count + 1。時間複雜度爲 O(n2)O(n^2),會超時。

class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        if not nums: return 0
        n = len(nums)
        count = 0
        for i in range(n):
            sum = 0
            for j in range(i, -1, -1):
                sum += nums[j]
                if sum == k: count += 1
        return count

思路二前綴和解法。假設目標值爲 k,對於任意子數組的和(假設爲 x),爲了使得這個子數組和能滿足目標值 k,就要找另一個子數組的和(假設爲 y),使得 y - x = k。

可以創建一個與原數組大小相同的新數組 dp,即前綴和數組。該數組每個位置上都保存了原數組從 0 到對應位置的所有元素的和。從後向前遍歷前綴和數組,每到一個位置,如果不等於目標值 target,則向前尋找是否有 dp[j] - target,找到一個 count 數就加 1。

class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        n = len(nums)
        prefix = [0]  # 前綴和數組要額外加一個0
        # 創建前綴和數組
        for i in range(n):
            prefix.append(prefix[i] + nums[i])
        # 遍歷計數
        count = 0
        for i in range(n, 0, -1):
            target = prefix[i] - k
            for j in range(i-1, -1, -1):
                if prefix[j] == target:
                    count += 1
        return count

這樣遍歷其實仍然是雙重循環,時間複雜度並沒有降低,運行一下依然超時。

思路二改進:考慮用空間換時間,使用 前綴和 + 哈希表。用哈希表存儲前綴和,鍵爲 “前綴和的值”,值爲 “該前綴和的出現次數”。從左向右遍歷前綴和數組,將當前前綴和的值存入哈希表中,如果已經存在則直接加 1,並在哈希表中查找是否有鍵等於 “當前前綴和 - target”,若存在則給計數器 count 加上該鍵對應的值。

j > i,則我們的目標是 prefix[j] - prefix[i] = target

  • 如果從左向右遍歷前綴和數組,每次來到的位置都是 j,在哈希表裏的查找目標就是 pre[i] = pre[j] - target
  • 如果從右向左遍歷前綴和數組,每次來到的位置都是 i,在哈希表裏的查找目標就是 pre[j] = target + pre[i]
class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        n = len(nums)
        prefix = [0]
        count, dict = 0, {0:1}
        # 從左向右遍歷
        for i in range(n):
            pre = prefix[i] + nums[i]
            prefix.append(pre)
            # 查找目標值,若無則返回-1
            c = dict.get(pre - k, -1) # 查找目標pre[i] = pre[j]-target
            if c != -1:
                count += c
            # 當前前綴和添加到哈希表(注意先找目標值,再添加到哈希表)
            if pre not in dict:
                dict[pre] = 1
            else:
                dict[pre] += 1
        return count

只需要一層循環,時間複雜度爲 O(n)。用哈希表加速運算的思路適合 不關心具體的解,僅關心解的個數 的情況。


1248. 統計「優美子數組」

思路:前綴和 + 差分。約定數組 prepre[i] 表示以第 i 個元素爲結尾的子數組 [nums[0]...nums[i]] 中奇數的個數,那麼 pre[i] 可以由前置狀態 pre[i-1] 計算得到,即:pre[i] = pre[i-1] + 1 if nums[i] 是奇數 else pre[i-1]

那麼題目要求 [j...i] 這個子數組的奇數個數恰好爲 k,可以將這個條件轉化爲 pre[i] - pre[j-1] == k

在實際求解時,從左向右計算 pre 數組中各個位置的狀態(即奇數個數),然後將這個個數登記在哈希表裏,生成鍵值對:“奇數個數-出現次數”,每次登記時,順便在哈希表看一看有沒有目標值 pre[j-1],如果有就可以將個數累加在最終的答案 sum 上。pre 數組並不需要真的建立,只需要維護前一個值即可。

時間複雜度O(n)O(n),遍歷長度爲 n 的數組
空間複雜度O(n)O(n),需要哈希表記錄奇數出現頻次,最壞情況下哈希表長度等於原數組

class Solution:
    def numberOfSubarrays(self, nums: List[int], k: int) -> int:
        n = len(nums)
        pre = 0
        res, dict = 0, {0:1}
        for i in range(n):
            pre = pre + 1 if nums[i] % 2 != 0 else pre
            dict[pre] = dict.get(pre, 0) + 1
            res += dict.get(pre - k, 0)
        return res
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章