LeetCode 862 力扣 和至少为 K 的最短子数组 单调栈+二分+trick

题目链接:和至少为 K 的最短子数组
题目描述:

给定一个数组,数组元素的值范围为 32 32 32位整型的范围,同时给定一个 k k k,找出和大于等于 k k k的最短非空子序列(即要求连续),返回这个最短子序列的长度,如果不存在则返回 − 1 -1 1

题解:

我们可以依次考虑以某个位置 i i i结尾的子序列,对于位置 i i i我们记 s u m [ i ] sum[i] sum[i]表示下标属于 [ 0 − i ] [0-i] [0i]的所有元素的和,如果我们要求以 i i i位置结尾的最短子序列(该子序列而和大于等于 k k k),就等价于找到一个尽可能大的下标 p p p使得 s u m [ i ] − s u m [ p − 1 ] ≥ k sum[i] - sum[p-1] \ge k sum[i]sum[p1]k,我们对不等式进行变形可以得到 s u m [ p − 1 ] ≤ s u m [ i ] − k sum[p-1] \le sum[i] - k sum[p1]sum[i]k,这说明我们需要维护 s u m [ p − 1 ] sum[p-1] sum[p1],而这里实际上可以使用单调栈维护,这是因为:如果一个下标 p ′ > p 且 s u m [ p ′ − 1 ] < s u m [ p − 1 ] p' > p且sum[p'-1] < sum[p-1] p>psum[p1]<sum[p1](同时需要注意 p ′ l e i p' le i plei),那么 s u m [ p − 1 ] sum[p-1] sum[p1]这条信息就没有用了,因为 s u m [ p ′ − 1 ] sum[p'-1] sum[p1]更小(这代表肯定满足上面的不等式)而且 p ′ p' p更大(这代表序列会更短)这样的话有用的# s u m [ p − 1 ] sum[p-1] sum[p1]信息是单调递增的,这样在查找的时候可以使用二分,这样复杂度就能从 O ( n 2 ) O(n^2) O(n2)优化到 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)
O ( n ) O(n) O(n)的算法:上面的过程实际上还可以优化,在遍历这个单调序列的时候,我们实际上可以不需要二分,而可以直接对单调序列进行遍历,这是因为对于 i i i位置,如果该序列里面的某一个元素满足 s u m [ p − 1 ] ≤ s u m [ i ] − k sum[p-1] \le sum[i] - k sum[p1]sum[i]k,对于 i ′ > i i' > i i>i位置来说 s u m [ p − 1 ] sum[p-1] sum[p1]是没有用的,因为如果此时依然满足 s u m [ p − 1 ] ≤ s u m [ i ‘ ] − k sum[p-1] \le sum[i‘] -k sum[p1]sum[i]k长度 i ′ − p + 1 > i − p + 1 i' - p + 1 > i - p + 1 ip+1>ip+1如果不满足该等式,那更不会被更新;如果一开始不满足 s u m [ p − 1 ] ≤ s u m p [ i ] − k sum[p-1] \le sump[i] - k sum[p1]sump[i]k,那么该数据就应该被保留下来。所以在遍历的过程中,每遍历一个元素如果满足了不等式就可以删除,这样该单调序列的每个元素会被访问常数次,整个数组会被遍历一次,复杂度是 O ( n ) O(n) O(n)

代码:

class Solution {
public:
    int shortestSubarray(vector<int>& nums, int k) {
        int n = nums.size();
        int ans = -1;
        s.resize(n + 1);
        int l = 0, r = 0; // 信息的有效区间
        long long sum = 0; // 前缀和
        s[r++] = {0, -1};
        for (int i = 0; i < n; i++) {
            sum += nums[i];
            bool move = false;
            while(l < r && s[l].first <= sum - k) {
                l++;
                move = true;
            };
            if (move) { // 移动意味着存在着满足要求的答案
                if (ans == -1) { ans = i -  s[l - 1].second; }
                else { ans = min(ans, i - s[l - 1].second); }
            }
            while(l < r && s[r - 1].first >= sum) { r--; }
            s[r++] = {sum, i};
        }
        return ans;
    }
private:
    // 用pair<long long, int>来保存信息
    // first表示某个位置的前缀和
    // second表示这个位置
    vector<pair<long long, int>> s;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值