560. 和为 K 的子数组
提示
给你一个整数数组
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
暴力枚举的基本思路是通过两层循环来枚举所有可能的子数组,然后计算每个子数组的和,判断其是否等于目标值 k
,如果相等则将计数器加 1。
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var subarraySum = function (nums, k) {
let count = 0;
for (let i = 0; i < nums.length; i++) {
let sum = 0;
for (let j = i; j < nums.length; j++) {
sum += nums[j];
if (sum === k) {
count++
}
}
}
return count;
};
给定一个整数数组 nums
和一个整数 k
,要找出数组中和为 k
的子数组(连续非空元素序列)的个数。例如,对于数组 [1, 1, 1]
和 k = 2
,子数组 [1, 1]
满足和为 2
,所以结果是 2
。
前缀和是指从数组开头到当前位置的所有元素的和。假设我们有一个数组 nums = [a0, a1, a2, ..., an]
,那么:
prefixSum[0] = a0
;prefixSum[1] = a0 + a1
;prefixSum[2] = a0 + a1 + a2
;prefixSum[i] = a0 + a1 + ... + ai
。对于计算子数组和,有一个重要的性质:如果我们要找从索引 j
到索引 i
(j <= i
)的子数组和,那么这个子数组和 subarraySum(j, i)
就等于 prefixSum[i] - prefixSum[j - 1]
(当 j > 0
时),如果 j = 0
,则 subarraySum(j, i) = prefixSum[i]
。
我们的目标是找到满足 subarraySum(j, i) = k
的子数组个数。根据上面的前缀和性质,subarraySum(j, i) = prefixSum[i] - prefixSum[j - 1] = k
,可以变形为 prefixSum[j - 1] = prefixSum[i] - k
。
这意味着,如果我们知道所有前缀和的值以及它们出现的次数,那么对于当前计算得到的前缀和 prefixSum[i]
,我们只需要检查之前是否出现过 prefixSum[i] - k
这个前缀和。如果出现过,那么就说明存在一个子数组的和为 k
,并且出现次数就是 prefixSum[i] - k
出现的次数。
为了高效地存储和查询前缀和及其出现的次数,我们使用哈希表(在 JavaScript 中是 Map
对象)。具体步骤如下:
初始化:
Map
对象 map
,用于存储前缀和及其出现的次数。map.set(0, 1)
,这是因为前缀和为 0
的情况是存在的(对应空数组,空数组的和为 0
),出现次数为 1
。sum = 0
来记录当前的前缀和,ans = 0
来记录和为 k
的子数组的个数。遍历数组:
num
,将其加到当前前缀和 sum
中,即 sum += num
。map
中是否存在 sum - k
。如果存在,说明存在一个子数组的和为 k
,将 map.get(sum - k)
的值加到 ans
中。这是因为 sum - (sum - k) = k
,map.get(sum - k)
表示之前出现过 sum - k
这个前缀和的次数,也就意味着有这么多组子数组的和为 k
。sum
及其出现的次数存入 map
中。如果 sum
已经存在于 map
中,将其出现次数加 1
;否则,将其出现次数设为 1
。返回结果:
ans
中存储的就是和为 k
的子数组的个数,将其返回。以输入 nums = [1, 1, 1]
,k = 2
为例:
map = {0: 1}
,sum = 0
,ans = 0
。num = 1
,sum = 0 + 1 = 1
。map
中是否有 1 - 2 = -1
,没有。map.set(1, 1)
(现在 map = {0: 1, 1: 1}
)。ans
不变,仍为 0
。num = 1
,sum = 1 + 1 = 2
。map
中是否有 2 - 2 = 0
,有,ans = ans + map.get(0) = 0 + 1 = 1
。map.set(2, 1)
(现在 map = {0: 1, 1: 1, 2: 1}
)。num = 1
,sum = 2 + 1 = 3
。map
中是否有 3 - 2 = 1
,有,ans = ans + map.get(1) = 1 + 1 = 2
。map.set(3, 1)
(现在 map = {0: 1, 1: 1, 2: 1, 3: 1}
)。ans = 2
,符合预期。通过这样的方式,使用前缀和与哈希表的方法能够高效地解决“和为 K 的子数组”问题。希望以上解释能帮助你理解这个算法的原理和实现过程。
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var subarraySum = function (nums, k) {
let map=new Map();
// 初始设置为0的元素有1个
map.set(0,1);
let sum=0;
let ans=0;
for(let num of nums){
// 计算元素的前缀和
sum+=num;
// 计数,统计前缀和为某个数共有多少个
map.set(sum,(map.has(sum)||0)+1)
// 判断map中是否有 sum-k 如果存在,说明存在一个子数组的和为 k
if(map.has(sum-k)){
// 将 map.get(sum - k)的值加到 ans 中
ans+=map.get(sum-k);
}
}
return ans;
};
239. 滑动窗口最大值
给你一个整数数组
nums
,有一个大小为k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的k
个数字。滑动窗口每次只向右移动一位。返回 滑动窗口中的最大值 。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 输出:[3,3,5,5,6,7] 解释: 滑动窗口的位置 最大值 --------------- ----- [1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7示例 2:
输入:nums = [1], k = 1 输出:[1]提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length
O(nk)会很慢
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
function maxSlidingWindow(nums, k) {
let result=[]
for (let i = 0; i < nums.length - k + 1; i++) {
let max = -Infinity;
for (let j = i; j < i + k; j++) {
max = Math.max(nums[j], max)
}
result.push(max)
}
return result
}
初始化数据结构:
q
数组模拟单调栈,它的作用是存储数组 nums
中元素的下标,并且保证栈内元素对应的 nums
值从栈底到栈顶是单调递减的。这样,栈顶元素对应的 nums
值始终是当前窗口内的最大值(或者候选最大值)。ans
数组用于存储每个滑动窗口的最大值,最终作为函数的返回结果。遍历数组过程:
nums[i]
考虑加入窗口时,通过 while
循环检查栈顶元素。如果当前元素 nums[i]
大于等于栈顶元素对应的 nums
值,就不断弹出栈顶元素。这是因为栈顶元素不可能是当前窗口内的最大值了,通过这样的操作保证了栈的单调性。i
压入栈 q
中,以便后续判断该元素是否在窗口内以及作为可能的最大值候选。i - k
)。如果是,说明该元素已经随着窗口的滑动移出了当前窗口,需要将其从栈顶弹出,以确保栈中元素对应的下标都在当前窗口内。i >= k - 1
,因为前 k - 1
个元素构不成完整窗口)时,栈顶元素对应的 nums
值就是当前窗口的最大值,将其加入到 ans
数组中。返回结果:遍历完整个数组 nums
后,ans
数组中已经存储了每个滑动窗口的最大值,将其返回。
给定的示例 nums = [1, 3, -1, -3, 5, 3, 6, 7]
,k = 3
为例,详细演示如何计算滑动窗口最大值的:
初始化
初始化 q
为一个空数组,用于模拟单调栈,存储数组元素的下标;初始化 ans
为一个空数组,用于存储每个滑动窗口的最大值。
i
初始化为 0
,开始遍历数组 nums
。
第一次循环 (i = 0
)
nums[0] = 1
,此时 q
为空,将 i = 0
压入 q
,即 q = [0]
。
因为 q
只有一个元素,它的下标 0
满足 0 >= 0 - 3
(此时窗口还未完全形成,这里条件看似不成立但后续会处理),不执行 q.shift()
。
由于 i = 0 < 3 - 1
,窗口还未形成,不执行 ans.push(nums[q[0]])
。
第二次循环 (i = 1
)
nums[1] = 3
,因为 3 >= nums[q[q.length - 1]]
(即 3 >= nums[0]
,nums[0] = 1
),执行 q.pop()
,此时 q = [1]
。
将 i = 1
压入 q
,q = [1]
。
因为 q[0] = 1
,1 < 1 - 3
不成立,不执行 q.shift()
。
由于 i = 1 < 3 - 1
,窗口还未形成,不执行 ans.push(nums[q[0]])
。
第三次循环 (i = 2
)
nums[2] = -1
,因为 -1 < nums[q[q.length - 1]]
(即 -1 < nums[1]
,nums[1] = 3
),不执行 q.pop()
。
将 i = 2
压入 q
,q = [1, 2]
。
因为 q[0] = 1
,1 < 2 - 3
不成立,不执行 q.shift()
。
由于 i = 2 >= 3 - 1
,窗口已经形成,执行 ans.push(nums[q[0]])
,即 ans = [3]
。
第四次循环 (i = 3
)
nums[3] = -3
,因为 -3 < nums[q[q.length - 1]]
(即 -3 < nums[2]
,nums[2] = 3
),不执行 q.pop()
。
将 i = 3
压入 q
,q = [1, 2, 3]
。
因为 q[0] = 1
,1 < 3 - 3
不成立,不执行 q.shift()
。
由于 i = 3 >= 3 - 1
,窗口已经形成,执行 ans.push(nums[q[0]])
,即 ans = [3, 3]
。
第五次循环 (i = 4
)
nums[4] = 5
,因为 5 >= nums[q[q.length - 1]]
(即 5 >= nums[3]
,nums[3] = -3
),执行 q.pop()
,此时 q = [1, 2]
。
因为 5 >= nums[q[q.length - 1]]
(即 5 >= nums[2]
,nums[2] = 3
),再执行 q.pop()
,此时 q = [1]
。
将 i = 4
压入 q
,q = [1]
。
因为 q[0] = 1
,1 < 4 - 3
不成立,不执行 q.shift()
。
由于 i = 4 >= 3 - 1
,窗口已经形成,执行 ans.push(nums[q[0]])
,即 ans = [3, 3, 5]
。
第六次循环 (i = 5
)
nums[5] = 3
,因为 3 >= nums[q[q.length - 1]]
(即 3 >= nums[1]
,nums[1] = 1
),执行 q.pop()
,此时 q = [5]
。
将 i = 5
压入 q
,q = [5]
。
因为 q[0] = 5
,5 < 5 - 3
不成立,不执行 q.shift()
。
由于 i = 5 >= 3 - 1
,窗口已经形成,执行 ans.push(nums[q[0]])
,即 ans = [3, 3, 5, 5]
。
第七次循环 (i = 6
)
nums[6] = 6
,因为 6 >= nums[q[q.length - 1]]
(即 6 >= nums[5]
,nums[5] = 3
),执行 q.pop()
,此时 q = [6]
。
将 i = 6
压入 q
,q = [6]
。
因为 q[0] = 6
,6 < 6 - 3
不成立,不执行 q.shift()
。
由于 i = 6 >= 3 - 1
,窗口已经形成,执行 ans.push(nums[q[0]])
,即 ans = [3, 3, 5, 5, 6]
。
第八次循环 (i = 7
)
nums[7] = 7
,因为 7 >= nums[q[q.length - 1]]
(即 7 >= nums[6]
,nums[6] = 6
),执行 q.pop()
,此时 q = [7]
。
将 i = 7
压入 q
,q = [7]
。
因为 q[0] = 7
,7 < 7 - 3
不成立,不执行 q.shift()
。
由于 i = 7 >= 3 - 1
,窗口已经形成,执行 ans.push(nums[q[0]])
,即 ans = [3, 3, 5, 5, 6, 7]
。
循环结束
遍历完整个数组 nums
后,ans
数组中存储了每个滑动窗口的最大值,最终返回 ans = [3, 3, 5, 5, 6, 7]
。
var maxSlidingWindow = function(nums, k) {
let q = []; // 用于模拟单调栈,存储数组元素的下标
let ans = []; // 用于存储每个滑动窗口的最大值
for (let i = 0; i < nums.length; i++) {
// 当栈不为空,并且当前元素大于等于栈顶元素对应的nums值时
while (q.length > 0 && nums[i] >= nums[q[q.length - 1]]) {
q.pop(); // 弹出栈顶元素,因为它不可能是当前窗口内的最大值了
}
q.push(i); // 将当前元素的下标压入栈中
// 如果栈顶元素的下标已经不在当前窗口范围内(即小于等于i - k)
if (q[0] <= i - k) {
q.shift(); // 弹出栈顶元素,因为它已经不在当前窗口内了
}
// 当窗口已经形成(即i >= k - 1,因为前k - 1个元素构不成完整窗口)
if (i >= k - 1) {
ans.push(nums[q[0]]); // 将栈顶元素对应的nums值(也就是当前窗口的最大值)加入到结果数组ans中
}
}
return ans;
};