难度标识:
⭐:简单,⭐⭐:中等,⭐⭐⭐:困难。
tips:这里的难度不是根据LeetCode难度定义的,而是根据我解题之后体验到题目的复杂度定义的。
1.和为 K 的子数组 ⭐⭐
思路
这题可以使用前缀和的思路。
-
前缀和:对于数组中的每一个位置
i,都可以求一个从位置0到位置i的累加和,称之为“前缀和”。假如位置j到位置i(j < i)的元素和为k,那么前缀和就满足preSum[i] - preSum[j-1] = k。 -
初始化:
-
preSum用来计算从位置0到当前位置i的前缀和。 -
res用来记录满足条件的子数组的数量。 -
map用来存放前缀和的值及其出现的次数。初始化为{0: 1}是因为如果某个前缀和正好为k,那么从位置0到当前位置的子数组满足条件,这时应该计算在结果内。
- 遍历数组:
-
对数组
nums进行遍历,不断更新preSum的值。 -
检查
preSum - k的值是否在map中,如果存在,说明从某个位置到当前位置的子数组和为k。这个位置的个数就是map.get(preSum - k),将这个数值加到结果res上。 -
更新
map的值,将当前的preSum的出现次数加1。
代码
var subarraySum = function (nums, k) {
let preSum = 0, res = 0;
const map = new Map()
map.set(0, 1)
for (let i = 0; i < nums.length; i++) {
preSum += nums[i]
if (map.has(preSum - k)) {
res += map.get(preSum - k)
}
map.set(preSum, (map.get(preSum) || 0) + 1)
}
return res
};
思考:上面的代码为什么不将
map.set(preSum, (map.get(preSum) || 0) + 1)这行代码放到if (map.has(preSum - k)) { res += map.get(preSum - k) }之前呢?
当我们输入的数组为 [1] ,k值为 0 的时候,如果按照上面将代码放到前面,计算出来的结果就不对,结果会变成1,其实结果是0。
2.滑动窗口最大值 ⭐⭐
思路
tips:如果看不懂可以看这个视频的讲解,单调队列的思路讲解的很清楚。
这题可以使用单调队列或者叫双端队列的解法。维护一个双端队列,队列中存储的是数组的索引,队列的头部始终为当前窗口的最大值的索引。
- 初始化双端队列:
- 使用一个双端队列(Deque)来维护窗口中的元素。关键点在于:这个队列中存储的是元素的索引,而不是元素的值。
- 处理队列的第一部分:
-
当队列非空,首先考察队列头部的索引是否还在窗口范围内,如果不在,则将头部索引弹出。
-
接着,当队列非空,且当前考察的元素值大于队尾所对应的元素值时,将队尾的索引弹出,直到队尾元素值大于当前考察元素值或队列为空。
-
将当前考察元素的索引放入队列的尾部。
- 滑动窗口:
-
当窗口开始滑动时(当窗口的大小大于等于k-1时,也就是窗口大小为k时开始取最大值加入结果数组中):
-
每次滑动,队列的头部索引对应的元素就是当前窗口的最大值。将这个值
push到结果数组中。
我们维护的这个队列是个双端单调递减(指索引对应的值)的队列,所以队列头部索引对应的元素是当前窗口的最大值。
代码
var maxSlidingWindow = function (nums, k) {
const q = [], res = [];
for (let i = 0; i < nums.length; i++) {
if (q.length && q[0] <= i - k) {
q.shift()
}
while (q.length && nums[i] >= nums[q[q.length - 1]]) {
q.pop()
}
q.push(i)
if (i >= k - 1) {
res.push(nums[q[0]])
}
}
return res
};
3.最小覆盖子串 ⭐⭐⭐
思路
这题可以使用滑动窗口的思路解决。
-
哈希表记录需求与窗口状态:利用两个对象,一个记录
t中字符的需求,另一个记录当前窗口内的字符状态。 -
双指针扫描:使用
left和right两个指针,动态调整窗口的大小。right指针向右移动扩大窗口,直到窗口中的字符满足t的需求。此时开始移动left指针,缩小窗口,直到窗口中的字符刚好或不满足t的需求。 -
持续更新最小子串信息:在滑动窗口的过程中,持续地记录并更新找到的满足条件的最小子串的起始位置和长度。
-
结果返回:根据记录的最小子串信息,从
s中提取并返回结果。如果未找到满足条件的子串,则返回空字符串。
这题如果你掌握了滑动窗口的技巧,那其实还是很简单的,滑动窗口其实就是套模板。如果不会也可以看看这个参考资料 以及我上篇滑动窗口的题目文章。
代码
var minWindow = function (s, t) {
const obj = {}
for (let ch of t) {
obj[ch] = (obj[ch] || 0) + 1
}
let left = 0, right = 0, count = 0;
const window = {}
const needLen = Object.keys(obj).length
let start = 0, len = Infinity
while (right < s.length) {
const c = s[right++]
if (obj[c]) {
window[c] = (window[c] || 0) + 1
if (obj[c] === window[c]) {
count++
}
}
while (count === needLen) {
if (right - left < len) {
start = left
len = right - left
}
const d = s[left++]
if (obj[d]) {
if (obj[d] === window[d]) {
count--
}
window[d]--
}
}
}
return len === Infinity ? '' : s.substr(start, len)
};