给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
示例 1 :
输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
暴力法还是比较好想的, 按照题目要求, 把每种情况都列出来, 然后统计出结果就行了. 总共需要O(n^2)的时间复杂度, 自然是结果正确, 但是超时.
class Solution {
func subarraySum(_ nums: [Int], _ k: Int) -> Int {
// 统计数量
var result = 0
let n = nums.count
for i in 0..<n {
// 计算i到j之间的和
var sum = 0
for j in i..<n {
sum += nums[j]
// 满足条件,数量+1
if sum == k {
result += 1
}
}
}
return result
}
}
暴力法不行, 然后就想到了滑动窗口来解决, 但是但是, 发现这个题目里可以有负数的, 滑动窗口要求数组里的都是正数, 这种方法也不行.
好吧, 忍不住看了题解, 题解采用了 前缀和 + 哈希表优化 , 对于数组, 我们计算好好一个新的数组, 新的数组中是前i项的和,
比如: 原始数组是: [5 , -1 , 1, 2, -2]
计算的前缀和是:[5, 4, 5 , 7 , 5],
对于第i项, 如果在sum中存在一个值sum[j]正好等于sum[i] - k, 那么就可以计数一次,
还是来一个例子, 比如k = 3; 正好存在sum[1] = sum[3] -k , 那就说明从下标为1开始的数字到下标为3的数字组成的数组满足了条件, 注意是(1,3], 左开右闭 , 就是 [1,2];
如果k=2, 会出现2个, [2], [-1 , 1, 2,], 需要统计出sum[j] == 5的个数, 所以需要考虑用一个字典了, key是前缀和, value是此前缀和的出现次数.
还有一种情况, 如果k==5, 有3个[5], [5 , -1 ,1], [5 , -1 , 1, 2, -2], 但是sum[i] - k却是一个都没有, 需要添加一个dic[0] = 1的值, 这样在计算sum[i] == k的时候不会遗漏.
上面的原始思路, 在做的过程中, 发现一些可以优化的点,
首先用来存储前缀和的数组不是必要的, 可以用一个sumI+hash表来优化 , 用一个hash表来存储前缀和出现的次数, 可以快速的知道sum[j]的出现次数. 当然, 经过优化后, 理解难度上也大了一点. 时间复杂度为O(n), 只需要一次遍历即可
class Solution {
func subarraySum(_ nums: [Int], _ k: Int) -> Int {
// 计算前项和
var sumI = 0
// 统计和为sum的个数
var dic = [Int:Int]()
// 记录一个初始值,
dic[0] = 1
var result = 0
let n = nums.count
for i in 0..<n {
let item = nums[i]
// 计算前I项的和
sumI += item
// 更新结果,如果字典里没有就result+0
result += dic[sumI-k] ?? 0
// 把前项和出现的次数更新到字典里
dic[sumI, default : 0] += 1
}
return result
}
}