和至少为 K 的最短子数组

92 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第30天,点击查看活动详情

862. 和至少为 K 的最短子数组 - 力扣(LeetCode)

给你一个整数数组 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^5
  • 1 <= 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;
};