前綴和
前綴和 就是數組 第 0 項 到 當前項 的總和。比如數組 nums
,那麼它的前綴和 prefixSum[x]
就表示 nums
從第 0 項到第 x 項的總和。
數組 nums 中任意一項就是兩個相鄰前綴和之差:
數組 nums 中第 i
項到第 j
項之和:
特別注意,由於 i
可以爲 0,i-1
會出現數組越界,所以前綴和數組一般需要在 0 位置擴充一個 0,便於計算數組 nums 中第 0 項到第 k 項之和。
前綴和數組每一項 dp[i]
都是原數組從第 0
項到第 i
項的總和,所以如果 j
大於 i
,那麼 dp[j]
就一定是包含 dp[i]
的。
相關題目
思路一:暴力解法,雙重循環,每次固定一個 i
,用 j
循環往前走,每到一個位置判斷子數組和是否爲 target,是則 count + 1。時間複雜度爲 ,會超時。
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)。用哈希表加速運算的思路適合 不關心具體的解,僅關心解的個數 的情況。
思路:前綴和 + 差分。約定數組 pre
中 pre[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
數組並不需要真的建立,只需要維護前一個值即可。
時間複雜度:,遍歷長度爲 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