leetcode862. 和至少为 K 的最短子数组 前缀和+单调队列

示例 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

错解

  • 使用双指针,滑动窗口难以处理负数的情况,比如下面的测试用例,第一个窗口无法得到大于89的值就会退出,返回-1.
  • [-28,81,-20,28,-29]89 输出:-1 预期结果:3
class Solution {
public:
    int shortestSubarray(vector<int>& nums, int k) {
        if(nums.size()==1){
            if(nums[0]>=k){return 1;}else{return -1;}
        }

        int l=0; int r =1;
        int sum=nums[0];
        int res =nums.size()+1;
        while(l<r){
            if(sum<k){
                if(r<nums.size()){
                    sum = sum+nums[r];
                    r++;
                }else{
                    //break;[84,-37,32,40,95] 167
                    l++;
                    sum = sum-nums[l-1];
                }

            }else{
                res = min(res,r-l);
                l++;
                sum = sum-nums[l-1];
            }
        }
    return (res == nums.size()+1) ? -1 : res;
    }
};

前缀和解法

数组的前缀和在算法和数据处理中有多种应用,以下是一些常见的作用:

  1. 快速计算区间和:通过使用前缀和,可以快速计算任意区间内的元素和。假设有一个数组的前缀和数组 prefix,要计算从位置 i 到位置 j 的区间和,只需计算 prefix[j] - prefix[i-1](如果 i 不为 0)。这种方法的时间复杂度为 O(1),相比于遍历区间内的元素求和的时间复杂度 O(n) 更高效。

  2. 寻找子数组的最大和或最小和:通过将原始数组转换为前缀和数组,可以将寻找子数组的最大和或最小和问题转化为寻找前缀和数组中的最大值或最小值的问题。这样可以利用动态规划或其他优化算法来解决,提高算法的效率。

  3. 寻找满足特定条件的子数组:通过前缀和数组,可以在一次遍历中查找满足特定条件的子数组。例如,可以使用双指针法在前缀和数组中查找和为特定值的子数组,或者查找和满足一定范围的子数组。

  4. 数组元素的更新和查询:如果需要频繁地对数组进行元素的更新和查询操作,使用前缀和可以提高效率。通过维护前缀和数组,可以在 O(1) 的时间复杂度内更新和查询任意位置的元素。

  5. 数据压缩:在某些情况下,原始数组中的元素可能具有一定的规律性,导致大量的重复值。通过计算前缀和,可以将原始数组转换为前缀和数组,从而实现数据的压缩。这样可以减少存储空间的使用,并且在某些操作中提高效率。

通过前缀和遍历所有区间暴力求解


class Solution {
public:
    int shortestSubarray(vector<int>& nums, int K) {
        const int n = nums.size();
        long long sum[n+1];
        sum[0] = 0;
        int res = nums.size()+1;
        for(int i = 0; i <nums.size(); ++i){
            sum[i + 1] = sum[i] + nums[i];
        }
        // 遍历sum数组,i,j为下标
        for(int i = 0; i <= n; ++i){
            // j < i+min:当子数组长度j-i已经超过当前最短值min时,停止遍历
            for(int j = i + 1; j < n && j-i < res; ++j){
                if(sum[j]-sum[i] >= K){
                    res = j-i;
                    break;
                }
            }
        }    
        return  return (res == nums.size()+1) ? -1 : res;
    }
};

优化

  • 需要一个单调递增的前缀和
  • i<j,如果存在s[i]>s[j](或者说i到j的区间和是负的),那么s[end]减去s[i]获得的值更小,且子数组长度更长,所以这时可跳过i位置的遍历,即考虑哪些和为正值的区间
class Solution {
public:
    int shortestSubarray(vector<int>& nums, int K) {
        const int n = nums.size();
        long long sum[n+1];
        sum[0] = 0;
        int res = n+1; //min用于记录满足要求的子数组长度
        for(int i = 0; i < n; ++i){
            if(nums[i] >= K)   return 1;
            sum[i + 1] = sum[i] + nums[i];
        }
        
        deque<int> dq; //存储的是下标
        for(int end = 0; end < n+1; ++end){
            // 当前end更优
            while(!dq.empty() && sum[dq.back()] >= sum[end]){
                dq.pop_back(); //之前的end被淘汰,保持栈的单调性
            }
            //如果之前的front到当前的end的所有可能
            while(!dq.empty() && sum[end] - sum[dq.front()] >= K  ){
                int len = end - dq.front();
                dq.pop_front(); 
                if(res > len) res = len;
            }
            dq.push_back(end); //更新现在的end
        }
        
        
        return (res == nums.size()+1) ? -1 : res;
    }
};

CG

除了数组的前缀和方法之外,还有其他类似的方法可以用于处理数组或序列的累计求和问题。以下是一些常见的方法:

  1. 后缀和:后缀和与前缀和相反,它是从数组末尾开始到当前位置的所有元素的累计和。计算方法与前缀和类似,只是方向相反。可以通过从数组末尾开始遍历并累加元素来计算后缀和。

  2. 差分数组:差分数组是指将原始数组中相邻元素之间的差值存储在新数组中。差分数组的第一个元素等于原始数组的第一个元素,而后续元素等于原始数组中相邻元素的差值。差分数组可以用于高效地进行区间更新操作,通过对差分数组进行前缀和运算可以还原出原始数组。

  3. 前缀积:类似于前缀和,前缀积是指将数组中从第一个元素开始到当前位置的所有元素相乘得到的新数组。计算方法与前缀和类似,只是将累加操作改为累乘操作。

  4. 双指针法:双指针法是一种常用的技巧,适用于需要在数组或序列中查找满足特定条件的子数组或子序列的问题。通过使用两个指针,可以在一次遍历中完成对数组的处理。

这些方法都是根据具体问题的需求而选择的,可以根据实际情况选择最适合的方法来解决累计求和问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值