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

  • https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-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

错解

  • 使用双指针,滑动窗口难以处理负数的情况,比如下面的测试用例,第一个窗口无法得到大于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;
    }
};
  • 没有负数的最大的优势是区间和具有“单调性”。单调性是一个非常好的性质,具有单调性的子数组相关问题可以很方便的使用二分查找、滑动窗口等方法解决,近两周的周赛都有这样的题(2444. 统计定界子数组的数目、2447. 最大公因数等于 K 的子数组数目)。

前缀和解法

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

  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;
    }
};

优化

  • 需要一个单调递增的前缀和
  • is[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. 双指针法:双指针法是一种常用的技巧,适用于需要在数组或序列中查找满足特定条件的子数组或子序列的问题。通过使用两个指针,可以在一次遍历中完成对数组的处理。

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

你可能感兴趣的:(笔记,算法)