题目
给你一个整数数组 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
题解
-
最基本的,暴力解法
两个边界,一定一遍历,需要O(n ^ 2);求和计算还需要 O(n);
最终时间复杂度O(n^3)。
超时。 -
优化1:对内部循环进行优化
求和计算与遍历过程可以综合优化。O(n)的求和计算可以改写为 O(1)过程。
优化后相当于只在乎尾部的遍历,需要O(n)计算量;
对头部指针的遍历和求和计算可以放在一起,每次遍历时求和,需要O(n)计算量;
优化后时间复杂度为O(n^2)。
- 不能一直是头不动尾动的思想
- 优化2:前缀和+哈希表
通过计算前缀和,将问题转化为求解两个前缀和之差等于k的情况。遍历一次,就可以统计出和为k的连续子数组的个数。时间复杂度为O(n),空间复杂度为O(n)。
假设数组的前缀和数组为prefixSum,其中prefixSum[i]表示从数组起始位置到第i个位置的元素之和。那么对于任意的两个下标i和j(i < j),如果prefixSum[j] - prefixSum[i] = k,即从第i个位置到第j个位置的元素之和等于k,那么说明从第i+1个位置到第j个位置的连续子数组的和为k。
通过遍历数组,计算每个位置的前缀和,并使用一个哈希表来存储每个前缀和出现的次数。在遍历的过程中,我们检查是否存在prefixSum[j] - k的前缀和,如果存在,说明从某个位置到当前位置的连续子数组的和为k,我们将对应的次数累加到结果中。
前缀和第一个值规定为0,例如以下情况:
nums = [1, 1], k=2
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 # 给前缀和为presum的个数加1
return count
作者:无穷升高的卡农
链接:https://leetcode.cn/problems/subarray-sum-equals-k/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- 目前Python两种方法都会超时。前缀和方法84超时。
解题
- 中等题没试暴力。
- 枚举优化
class Solution(object):
def subarraySum(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
result = 0
for i in range(len(nums)):
tail = i
head = tail
t = 0
while head >= 0:
t += nums[head]
if t == k:
result+=1
head -= 1
return result
- 前缀和方法
class Solution(object):
def subarraySum(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
presum = {}
presum[0] = 1
result = 0
s = 0
for i in nums:
s += i
if s-k in presum.keys():
result+=presum[s-k]
if s in presum.keys():
presum[s] += 1
else:
presum[s] = 1
return result
问题
- collections.defaultdict的用法,免去了很多判断,代码比较简洁。
- 以前都是定前动后,现在动后定前,需要打破思维惯性。
- 前缀和方法
- 前缀和初值为0