持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第30天,点击查看活动详情
给你一个整数数组 nums 和一个整数 k ,找出 nums 中和至少为 k 的 最短非空子数组 ,并返回该子数组的长度。如果不存在这样的 子数组 ,返回 -1 。
子数组 是数组中 连续 的一部分。
示例 1:
输入: nums = [1], k = 1
输出: 1
示例 2:
输入: nums = [1,2], k = 4
输出: -1
示例 3:
输入: nums = [2,-1,2], k = 3
输出: 3
提示:
1 <= nums.length <= 10^5-10^5 <= nums[i] <= 10^51 <= k <= 10^9
思路
本题我们可以暴力求解,双层循环求各个子数组的和,时间复杂度为n^2,当n较大时,时间超时。
可以用前缀和 + 单调双端队列优化算法。我们定于一个长度为n + 1的数组preSums来存储nums的前缀和,preSums[i]表示nums的前i项数据和,则子数组nums[i]...nums[j]的和可以用preSums[j+1]-preSumns[i]求得。遍历preSums并维护一个preSums的升序的下标队列idxs,设sum = preSums[i],当sum - preSums[idxs[0]] >= k 时,表示nums子数组nums[idxs[0]]到nums[i - 1]的和大于等于k,满足条件,可以作为备选解,又因为idxs中对应的preSums单调递增,idxs[0]不需要重复比较,可以出队列,sum和下个值重复上述比较,最后从备选解中找出最小的值就是我们要求的解。
解题
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var shortestSubarray = function (nums, k) {
const n = nums.length;
const preSums = new Array(n + 1).fill(0);
for (let i = 0; i < n; i++) {
preSums[i + 1] = preSums[i] + nums[i];
}
let res = Number.MAX_VALUE;
const idxs = [];
for (let i = 0; i <= n; i++) {
const sum = preSums[i];
while (idxs.length && sum - preSums[idxs[0]] >= k) {
res = Math.min(res, i - idxs.shift());
}
while (idxs.length && preSums[idxs.at(-1)] >= sum) {
idxs.pop();
}
idxs.push(i);
}
return res === Number.MAX_VALUE ? -1 : res;
};