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