LeetCode1031. Maximum Sum of Two Non-Overlapping Subarrays

[Med] LeetCode 1031. Maximum Sum of Two Non-Overlapping Subarrays

链接:https://leetcode.com/problems/maximum-sum-of-two-non-overlapping-subarrays/

题目描述
Given an array A of non-negative integers, return the maximum sum of elements in two non-overlapping (contiguous) subarrays, which have lengths L and M. (For clarification, the L-length subarray could occur before or after the M-length subarray.)

Formally, return the largest V for which V = (A[i] + A[i+1] + … + A[i+L-1]) + (A[j] + A[j+1] + … + A[j+M-1]) and either:

0 <= i < i + L - 1 < j < j + M - 1 < A.length, or
0 <= j < j + M - 1 < i < i + L - 1 < A.length.

给出非负整数数组 A ,返回两个非重叠(连续)子数组中元素的最大和,子数组的长度分别为 L 和 M。(这里需要澄清的是,长为 L 的子数组可以出现在长为 M 的子数组之前或之后。)

Example 1:

Input: A = [0,6,5,2,2,5,1,9,4], L = 1, M = 2
Output: 20
Explanation: One choice of subarrays is [9] with length 1, and [6,5] with length 2.

Example 2:

Input: A = [3,8,1,3,2,1,8,9,0], L = 3, M = 2
Output: 29
Explanation: One choice of subarrays is [3,8,1] with length 3, and [8,9] with length 2.

Example 3:

Input: A = [2,1,5,6,0,9,5,0,3,8], L = 4, M = 3
Output: 31
Explanation: One choice of subarrays is [5,6,0,9] with length 4, and [3,8] with length 3.



Tag: Pre-Sum
解题思路
这道题目让我们找到两个不相交且连续的子数组,使得这两个数组的和可以组成最大值。一看就是标准版的pre-sum来解,可惜一开始没有想到O(n)的做法,只有一个O(n^2)的做法。

解法一:
这个做法是lee神给出来的,我加上了自己的解释
Lsum, sum of the last L elements
Msum, sum of the last M elements

Lmax, max sum of contiguous L elements before the last M elements.
Mmax, max sum of contiguous M elements before the last L elements/

首先解释一下这个算法的核心,我们首先还是需要计算这个数组的pre-sum。在这里我们不需要新开一个数组,而是直接在原数组上面更新就可以了。

然后我们需要三个变量,第一个用来储存结果,res。第二个和第三个叫Lmax和Mmax。作用如下,
我们首先将res赋值为A[L+M-1],意思是初始化结果为前L+M个数的和。Lmax是初始化为前L个数的和,Mmax初始化为前M个数的和。假设我们遍历到了位置A[i]。我们采用如下手段更新Lmax的值,也就是截止目前为止长度为L的subarray的最大值。为了使得两个subarray位置不重合,我们需要给M先预留一个长度为M的subarray,这个subarray的区间位置是当前位置i到i-M这个范围。所以实际上Lmax是在[0, i-M]这一个范围内长度为L的最大subarray的和。那么我们每往后移动一位,对应的Lmax就需要和(A[i - M] - A[i - M - L])更新一下。我们对Mmax做同样的事情。那么可能我们可能就有问题了,这样对于Lmax和Mmax会不会没办法覆盖到所有的区间呢?其实不会的,因为无论如何,M和L都不能取重合的位置,所以实际操作下,我们对于每一个位置,都分别计算在固定M subarray位置的情况下,可以找到的最大的L subarray数值是多少。和固定L subarray位置的情况下,可以找到的最大的M subarray数值是多少。在每一步的最后我们将提前固定的M Subarray和Lmax取和, 和固定的L subarray和Mmax取和,最后和re进行比较。求得在i位的最大值。如此重复直到最后一位。

class Solution {
   public int maxSumTwoNoOverlap(int[] A, int L, int M) {
        for (int i = 1; i < A.length; ++i)
            A[i] += A[i - 1]; 
        int res = A[L + M - 1], Lmax = A[L - 1], Mmax = A[M - 1];
        for (int i = L + M; i < A.length; ++i) {
            Lmax = Math.max(Lmax, A[i - M] - A[i - M - L]);
            Mmax = Math.max(Mmax, A[i - L] - A[i - L - M]);
            res = Math.max(res, Math.max(Lmax + A[i] - A[i - M], Mmax + A[i] - A[i - L]));
        }
        return res;
    }
}

解法二:
这是第一次做的,相当于O(n2)了,但是实际上要快一些因为对于长度为len的数组A来说,只是计算了 (len-M-L)次.这个做法的意思是,我们先固定L的窗口位置,然后遍历除L窗口位置的整个数组。寻找与L匹配的最大的M的窗口和。

class Solution {
    public int maxSumTwoNoOverlap(int[] A, int L, int M) {
        int res =0;
        int[] preSum = new int[A.length];
        int pre =0;
        for(int i=0; i<A.length; i++){
            pre+=A[i];
            preSum[i]  = pre;
        }
        
        for(int i=0; i<= A.length-L; i++){
            int lSum = preSum[i+L-1]- (i== 0? 0: preSum[i-1]);
            int mSum = 0;
            for(int j = i+L; j<= A.length-M; j++){
                mSum = Math.max(mSum, preSum[j+M-1]- (j== 0? 0: preSum[j-1]));
            }
            for(int j = 0; j<= i-M; j++){
                mSum = Math.max(mSum, preSum[j+M-1]- (j== 0? 0: preSum[j-1]));
            }
    
            res = Math.max(res, lSum+mSum);
        }
        
        return res;
    }
}

解法三:

这个也是O(n)的时间和空间,但是相对比较复杂。我的思路是,这道题目的关键点其实在于,假设当我们以M窗口范围进行遍历
原数组的时候,如何最快速度找到这个窗口之外的位置上的最大L窗口喝是多少。反过来,以L窗口的范围遍历的时候,也要找到除其之外最大的M窗口的和。
那么首先还是计算数组的Pre-sum。然后此时我们创建两个长度为A.length()(ps,只是为了方便计算)的数组,一个从后面往前计算L的窗口在每一位的最大值。比如说L = 2, 数组长度为8。 那我们要依次计算[6,7],[5,6],…,[0,1]的每一个长度为2的滑动窗口,并且在新数组maxOfL内对每一个位置更新我们从后往前遇到的 窗口长度为L的 最大和。
同样我们对maxOfL也做一样的事情。当我们有了这两个数组之后。此时我们从前往后,依次找到L窗口截止在位置i的最大值加上从maxOfM()中获取第i位的值,which is从后面往前计算的M最大值。可以将它当做以i点划分,左边是寻找L最大值的范围,右边从maxOfM()数组中获取到的M最大值。同样的事情我们也要从前往后对M也要做一遍。每一次我们都更新一下sum. 最后返回最大值。

class Solution {
    public int maxSumTwoNoOverlap(int[] A, int L, int M) {
        int[] presum = new int[A.length+1];
    
        for(int i=1; i<=A.length; i++){
            presum[i] = presum[i-1] + A[i-1];
        }
        
        int[] maxOfL = new int[A.length], maxOfM = new int[A.length];
        int mL =0, mM = 0;
        for(int i=A.length-1; i>=0; i--){
            if(i+L <= A.length){
                mL = Math.max(mL, presum[i+L]-presum[i]);
                maxOfL[i] = mL;
            }
            if(i+M <= A.length){
                mM = Math.max(mM, presum[i+M]-presum[i]);
                maxOfM[i] = mM;
            }
        }
        int sum =0, maxl =0, maxm = 0;
        for(int i=0; i<A.length; i++){
            if(i>=M){
                maxm = Math.max(maxm, presum[i]-presum[i-M]);
                sum = Math.max(sum, maxm+maxOfL[i]);
            }
            if(i >=L){
                maxl = Math.max(maxl, presum[i]-presum[i-L]);
                sum = Math.max(sum, maxl+maxOfM[i]);
            }
        }

        return sum;
        
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值