这是我参与8月更文挑战的第26天,活动详情查看:8月更文挑战
和为K的子数组(题号560)
题目
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
示例1:
输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
说明:
- 数组的长度为
[1, 20,000]。 - 数组中元素的范围是
[-1000, 1000],且整数k的范围是[-1e7, 1e7]。
链接
解释
这题啊,这题是经典哈希表小题目。
虽说是哈希表题目,但正常人看到这题的第一想法就是暴力求解,不管那么多,写出来就行了,优化什么的后续再说。
回到题目本身,这题的重点就是连续的子数组的个数,这个连续给出来后就十分简单了。
要想求整个数组中连续的子数组和为k的总个数,那必须要遍历数组中的每个元素,求出每个位置出现和为k区间的个数,没到一个新的位置都需要做相应的处理。
那要做什么处理呢?首先,因为是连续的,所以需要倒推来进行操作,从后往前进行和的累计,如果遇到k了,那就在count上加一,注意,此时不能停止累加,因为数组中元素有可能是负值,也就是说继续累加有可能出现第二个k,如果上一个数字是0也有可能出现多个k的情况。
暴力法的介绍就到这里了,之后跑了一下,直接超时,以为自己的思路哪里有问题,这一步暂且不表。事后看了下官方答案,发现他也用的这种方法,并且是推荐的第一种。笔者寻思这思路也没啥不一样啊,改了半天不好使,那不如直接复制下官方答案跑一手,结果您猜怎么着?**的也超时了!这谁能想得到,真是活久见。
既然这个方法也超时了,那还有没有别的办法呢?显然是有的,那就是我们的哈希表。
这方法笔者也没想出来了,最后也是看的官方答案,这里就按照官方解释的思路来介绍了。
先定义一个数组pre,别问我为啥不叫sum,因为官方就是这么叫的。这个数组用来存什么呢?用来存每个位置到数组开头元素的和,那么现在pre[i]就代表这数组从0到i中所有数字的和。
那根据题意,需要找出[i, j]区间内数字和为k的所有可能性,换成上面的公式就是:
pre[i] - pre[j - 1] === k
交换下位置可以得到这个公式:
pre[j - 1] === pre[i] - k
那么此时只需要考虑整个数组中有多少个和为pre[i] - k的pre[j]即可,先搞他一个Map来存储,这里为什么不用数组呢?就是因为有重复和结果的出现,如果用正常的DP数组来存储,必然会出现重复值的情况,这就不好累计了,如果使用Map,可以在原值上增加个数,这就简单不少了。
回到这个Map,这个Map用和作为key,出现次数作为value,只要在循环数组的过程中不断更新Map,累计目标值出现的次数,最后即可拿到我们想要的结果。
那在循环内部具体要做什么操作呢?
首先需要判断Map中有没有pre[i] - k的值,如果有,在count上进行累计操作
接下来在Map上设置当前pre的值,如果当前位置到数组头部的元素和已经存在了,那就自增一,否则初始化赋值为1即可。
到这也就完事了,其实感觉这题看看就得了,前端面试基本上遇不到。
自己的答案(暴力)
var subarraySum = function(nums, k) {
let count = 0
for (let i = 0; i < nums.length; i++) {
let sum = 0
for (let j = i; j > -1; j--) {
sum += nums[j]
if (sum === k) {
count++
}
}
}
return count
};
经典小暴力,最后的结果也是经典小超时,倒在了最后一个测试用例上,木得办法,官方答案也是倒在了最后一个测试用例上。
更好的方法(哈希表)
哈希表的思路在上面说过了,现在来看看具体实现👇:
var subarraySum = function(nums, k) {
const map = new Map()
map.set(0, 1)
let count = 0
let pre = 0
for (const num of nums) {
pre += num
if (map.has(pre - k)) {
count += map.get(pre - k)
}
map.set(pre, (map.has(pre) ? map.get(pre) : 0)+ 1)
}
return count
};
这里要注意的有两点:
-
由于是从左往右遍历,
pre就不用存成一个数组了,用一个值来累计即可 -
关于
map.set(0, 1)这块内容记得疼悦悦宝贝哦~老哥说的很好:
关于mp.put(0, 1); 这一行的作用就是为了应对 nums[0] +nums[1] + ... + nums[i] == k 的情况的, 也就是从下标 0 累加到下标 i, 举个例子说明, 如数组 [1, 2, 3, 6], 那么这个数组的累加和数组为 [1, 3, 6, 12] 如果 k = 6, 假如map中没有预先 put 一个 (0, 1) , 如果此时我们来到了累加和为 6 的位置, 这时map中的情况是 (1, 1), (3, 1), 而 mp.containsKey(pre - k) , 这时 pre - k 也就是 6 - 6 = 0, 因为 map 中没有 (0, 1) 所以 count 的值没有加一, 其实这个时候我们就是忽略了从下标 0 累加到下标 i 等于 k 的情况, 我们仅仅是统计了从下标大于 0 到某个位置等于 k 的所有答案
想看更多内容的同学可以点击这里,老哥说的很详细,这里不多赘述
理解完这两点后这种解法就没什么困难的了,内部逻辑并不复杂,只是某些细小的点可能不好太GET,慢慢来嘛~
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇