【哈希表】LeetCode 560. 和爲K的子數組【中等】

給你一個整數數組 nums 和一個整數 k ,請你統計並返回 該數組中和爲 k 的子數組的個數 。

 

示例 1:

輸入:nums = [1,1,1], k = 2
輸出:2


示例 2:

輸入:nums = [1,2,3], k = 3
輸出:2
 

提示:

1 <= nums.length <= 2 * 104
-1000 <= nums[i] <= 1000
-107 <= k <= 107


分析

方法一:暴力解法

該題目是要找到元素和等於k的所有連續子數組的個數。那麼怎麼求一個數組的連續子數組呢。可以使用兩層循環來實現。

n = len(nums)

subnums = []
for i in range(n):
    for j in range(i, n):
        subnums.append(nums[i:j+1])

既然這樣,稍作修改,就可以求元素和爲k的連續子數組的個數了。

class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int: 
        n = len(nums) # 求原始數組長度
        count = 0 # 初始化子數組個數

        for i in range(n):
            for j in range(i, n):
                if sum(nums[i: j+1]) == k:
                    count += 1        

        return count

提交代碼,提示超出時間限制。

很明顯,這麼做的時間複雜度是O(n3)。因爲除了兩層for循環,還有一層求和。

其實,可以對上面方法做一些優化。因爲我們求 nums[i: j + 1] 的下一次nums[i : j + 1 + 1],其實只比前一次多了一個元素nums[j + 1]。那麼我們就沒必要每次都從i開始算了。但是這麼做時間複雜度依然有O(n2),依然超時。

class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int: 
        n = len(nums) # 求原始數組長度
        count = 0 # 初始化子數組個數

        for i in range(n):
            sum = 0
            for j in range(i, n):
                sum += nums[j]
                if sum == k:
                    count += 1
        
        return count

前綴和

什麼是前綴和:前綴和是指一個數組的某下標之前的所有數組元素的和(包含其自身)。

通常,會在前綴和首位放一個0,比如數組[1,2,3],其前綴和是[0,1,3,6]。

前綴和通常可以幫助我們快速計算某個區間內的和。比如我們要計算i, j之間的和,那麼就是nums[i] + nums[i+1] + ... + nums[j],可以看作是:

nums[0] + nums[1] + ... + nums[i-1] + nums[i] + nums[i+1] + ... + nums[j] - 
nums[0] + nums[1] + ... + nums[i-1]

這個式子也是preSum[j] - preSum[i-1]。

那麼,我們先遍歷一次數組,求出前綴和數組。之後這個數組可以代替我們最開始暴力解法的sum函數。但是很遺憾,這種做法複雜度依然還是O(n2)。

class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        # 要求的連續子數組
        count = 0
        n = len(nums)

        preSum = [0]

        # 求前綴和數組
        tmp = 0
        for i in range(n):
            tmp += nums[i]
            preSum.append(tmp)
        
        # 求和爲k的連續子數組,求i到j之間的和
        for i in range(1, n+1):
            for j in range(i, n+1):
                if preSum[j] - preSum[i-1] == k:  # preSum[j] - preSum[i-1]代表着在nums數組中,前j個數之和減去前i-1個數之和
                    count += 1
        
        return count

進一步優化的話,我們可以邊計算前綴和,邊統計。遍歷過程中,我們統計歷史中每一個前綴和出現的次數,然後計算到位置i(含i)的前綴和preSum減去目標k在歷史上出現過幾次,假如出現過m次,代表第i位以前不含(不含i)有m個連續子數組的和爲presum - k,這m個和爲presum - k的連續子數組,每一個都可以和presum組合成爲presum - (presum - k)= k

上述示意圖,紅色的是當前遍歷到的前綴和presum,加入它之前有兩個前綴和等於presume - k(藍色區域範圍),那麼很明顯就會有兩個連續子數組的元素和爲k,對應橙色區域範圍。

class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        # 要求的連續子數組
        count = 0
        n = len(nums)
        preSums = collections.defaultdict(int)
        preSums[0] = 1

        presum = 0
        for i in range(n):
            presum += nums[i]

            # if preSums[presum - k] != 0
            count += preSums[presum - k] # 利用defaultdict的特性,當presum-k不存在時,返回的是0。這樣避免了判斷
            preSums[presum] += 1
        
        return count

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章