给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
示例 1 :
输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
方法一:暴力法,会超时。
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
cnt, n = 0, len(nums)
for i in range(n):
for j in range(i, n):
if (sum(nums[i:j + 1]) == k): cnt += 1
return cnt
方法二:前缀和,会超时。
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
cnt, n = 0, len(nums)
pre = [0] * (n + 1)
for i in range(1, n + 1):
pre[i] = pre[i - 1] + nums[i - 1]
for i in range(1, n + 1):
for j in range(i, n + 1):
if (pre[j] - pre[i - 1] == k): cnt += 1
return cnt
方法三:前缀和+字典
第一点:
使用前缀数组的一大好处是可用 pre(b) - pre(a-1) 来表示 a->b 中这段中数组的和。这个 a-1 可以不用那么纠结。重点理解思想。
比如 前5项的和 - 前3项的和 = 第4项~第5项的和。 (1+2+3+4+5) - (1+2+3) = 4+5
或者说 pre(b) - pre(a) 等同于 a+1~b 的和。这样子更好理解一些。
第二点:
因为这个列表是有负数的。因此前缀和不是单调递增的。相应的前缀和之差也是。比如有个点的前缀和是10,我们目标值是3,那么倒回去找一个前缀和为7的点。可能会有两个。如图所示。
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
# num_times 存储某“前缀和”出现的次数,这里用collections.defaultdict来定义它
# 如果某前缀不在此字典中,那么它对应的次数为0
num_times = collections.defaultdict(int)
num_times[0] = 1 # 先给定一个初始值,代表前缀和为0的出现了一次
cur_sum = 0 # 记录到当前位置的前缀和
res = 0
for i in range(len(nums)):
cur_sum += nums[i] # 计算当前前缀和
if cur_sum - k in num_times: # 如果前缀和减去目标值k所得到的值在字典中出现,即当前位置前缀和减去之前某一位的前缀和等于目标值
res += num_times[cur_sum - k]
# 下面一句实际上对应两种情况,一种是某cur_sum之前出现过(直接在原来出现的次数上+1即可),
# 另一种是某cur_sum没出现过(理论上应该设为1,但是因为此处用defaultdict存储,如果cur_sum这个key不存在将返回默认的int,也就是0)
# 返回0加上1和直接将其置为1是一样的效果。所以这里统一用一句话包含上述两种情况
num_times[cur_sum] += 1
return res
参考链接:https://leetcode-cn.com/problems/subarray-sum-equals-k/solution/xiong-mao-shua-ti-python3-qian-zhui-he-zi-dian-yi-/
参考链接:https://leetcode-cn.com/problems/subarray-sum-equals-k/solution/python3-qian-zhui-he-hashmap-by-mu-ren-6/