每日一题——和至少为K的最短子数组

给你一个整数数组 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 <= 105
  • -105 <= nums[i] <= 105
  • 1 <= k <= 109

思路:

1.暴力法(会超时)

显然这里需要用到前缀和数组,数组s[i]代表nums的前i个元素的和,s[j]-s[i]则代表从i到j的这一区间的和。

然后第一层循环从0开始,下一层循环从上一层的下一个位置开始,找满足s[j]-s[i]>=k条件的点,记住它们的长度,重复这个过程,知道找到最小值。

2.单调队列

遍历 s数组,访问过的前缀和先暂存在队列 q 中。根据前缀和数组的性质,后访问到的某个前缀和s[j] 减去之前访问到的某个前缀和,即为 nums 中某段子数组的和。因此,每次访问到某个前缀和 s[j] 时,可以用它减去队列 q中所有已经访问过的前缀和。当 q 中的某个前缀和 s[i],第一次出现 s[j]−s[i]≥k 时,就找到了以它i为起点的最短子数组的长度 j-i。此时,可以将它从 q中移除,后续即使还有以它为起点的满足条件的子数组,长度也会大于当前的长度。当一个前缀和 s[j] 试减完 q中的元素时,需要将它也放入 q中。将它放入 q前, q中可能存在比 s[j]大的元素,而这些元素和s[j] 一样,只能作为再后续访问到的某个前缀和s[h] 的减数。而作为减数时,更大的值只会让不等式 s[h]−s[i]≥k 更难满足。即使都满足,后访问到的值也可以带来更短的长度。 因此,在把 s[j] 放入 q时,需要将 q中大于等于s[j] 的值也都移除。

队列q中暂存的元素是的下标,对应下标的前缀和严格单调递增。

class Solution {
public:
    int shortestSubarray(vector<int>& nums, int k) {
        int n = nums.size();
        vector<long> preSumArr(n + 1);
        for (int i = 0; i < n; i++) {
            preSumArr[i + 1] = preSumArr[i] + nums[i];
        }
        int res = n + 1;
        deque<int> qu;
        for (int i = 0; i <= n; i++) {
            long curSum = preSumArr[i];
            while (!qu.empty() && curSum - preSumArr[qu.front()] >= k) {
                res = min(res, i - qu.front());
                qu.pop_front();
            }
            while (!qu.empty() && preSumArr[qu.back()] >= curSum) {
                qu.pop_back();
            }
            qu.push_back(i);
        }
        return res < n + 1 ? res : -1;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值