原题目
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.
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.
复制代码
题目翻译
给出一个正整数数组 A,并给出两个数值 L、M; 求这个数组中长度为 L 和 M 的,且无重复元素的子数组的元素之和的最大值。
也就是求 V = (A[i] + A[i+1] + ... + A[i+L-1]) + (A[j] + A[j+1] + ... + A[j+M-1])
的最大值,其中
- (A[i] + A[i+1] + ... + A[i+L-1]) 是长度为 L 的子数组
- (A[j] + A[j+1] + ... + A[j+M-1]) 是长度为 M 的子数组
并且 i、j、L、M 满足:
- 0 <= i < i + L - 1 < j < j + M - 1 < A.length, or
- 0 <= j < j + M - 1 < i < i + L - 1 < A.length.
答案 (JavaScript)
/**
* @param {number[]} A
* @param {number} L
* @param {number} M
* @return {number}
*/
var maxSumTwoNoOverlap = function(A, L, M) {
const prefixSum = [], length = A.length
prefixSum[0] = A[0]
for (let i=1; i<length; i++) {
prefixSum[i] = prefixSum[i-1] + A[i]
}
if (L + M === length){
return prefixSum[length - 1];
}
return Math.max(split_and_find_max_sum(A, prefixSum, L, M),
split_and_find_max_sum(A, prefixSum, M, L));
};
var split_and_find_max_sum = (A, prefixSum, L, M) => {
const length = A.length
const leftMax = new Array(length), rightMax = new Array(length);
for (let i = L-1; i<length; i++) {
const tmpSum = prefixSum[i] - prefixSum[i - L + 1] + A[i - L + 1];
if (i === L - 1) {
leftMax[i] = tmpSum;
} else {
leftMax[i] = Math.max(leftMax[i - 1], tmpSum);
}
}
for (let i = length - M; i >= 0; i--) {
const tmpSum = prefixSum[i + M - 1] - prefixSum[i] + A[i];
if (i === length - M) {
rightMax[i] = tmpSum;
} else {
rightMax[i] = Math.max(rightMax[i + 1], tmpSum);
}
}
let sum = -Infinity
for (let i = L - 1; i < length - M; i++) {
sum = Math.max(sum, leftMax[i] + rightMax[i + 1]);
}
return sum
}
复制代码
代码解析
如何高效的求子数组的和
这道题需要找到子数组元素和的最大值,那么肯定就需要求很多很多很多不同子数组的元素和,然后进行比对。
每次都将选出来的子数组元素逐一相加肯定是不合算的。
这里的方法是,建立一个 prefixSum 数组:
const prefixSum = [];
prefixSum[0] = A[0];
for (let i=1; i<length; i++) {
prefixSum[i] = prefixSum[i-1] + A[i]
}
复制代码
这样,对于数组 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
,如果我们想求 2-7 这个子数组的和,只需要用 prefixSum[6] - prefixSum[0]
就行了。
如何避免子数组重叠
这道题最容易想到的方法其实就是 —— 暴力搜索:
即,将所有长度为 L 和 M 的子数组都找出来,两两配对,每一对都判断一下它们是不是重合,如果重合就舍弃;如果没有重合部分,就计算这两个数组的和,然后找出最大值即可。
但是,效率真的低啊。。。虽然打着开心的 Accepted,但是。。。
Runtime:
100 ms, faster than 16.16% of JavaScript online submissions
for Maximum Sum of Two Non-Overlapping Subarrays.
复制代码
不爽 =。=
所以该怎么办呢?
不用暴力搜索,那么就要聪明的搜索。 这道题有个很关键的可以「聪明搜索」的点,就是 non-overlapping
:既然必须是无重合的数组对,我们一开始只需要寻找无重合的配对来求和就可以了。
如何寻找?
...怎么才能让同桌的领地和自己的领地不要重合呢?...画个三八线呗 =。=
同理,在数组中找一条**「分界线」**,在这条线两边分别寻找子数组,并让每个子数组的求和尽可能大。 最后找到所有分界情况下的子数组求和的最大值,就是答案了。
例如:有一个数组,[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
,选取数字 4,5 之间作为分界线。那么两个子数组 C、D 分别在 [1, 2, 3, 4]
和 [5, 6, 7, 8, 9, 10]
中寻找,就一定能保证数组 C、D 不重合。同理,我们还可以把分界线选在 6,7 之间...
这样,比暴力搜索就节省了很多次无用的比较。
函数 split_and_find_max_sum
其实就是在做画分割线 & 寻找最大可能的和这件事情。
不过在函数中,它先分别从前往后(从后往前)计算了所有可能分界线的左边(右边)可能子元素的最大值。
这个是从前往后找:
for (let i = L-1; i<length; i++) {
const tmpSum = prefixSum[i] - prefixSum[i - L + 1] + A[i - L + 1];
if (i === L - 1) {
leftMax[i] = tmpSum;
} else {
leftMax[i] = Math.max(leftMax[i - 1], tmpSum);
}
}
复制代码
这个 leftMax 就可以认为是,当分界线选了 i,i+1 之间的时候,分界线左边所有长度为 L 的子数组元素求和的最大值
这个是从后往前找:
for (let i = length - M; i >= 0; i--) {
const tmpSum = prefixSum[i + M - 1] - prefixSum[i] + A[i];
if (i === length - M) {
rightMax[i] = tmpSum;
} else {
rightMax[i] = Math.max(rightMax[i + 1], tmpSum);
}
}
复制代码
rightMax 和 leftMax 基本同理。只不过 rightMax 选子数组的范围是线右边,同时数组长度是 M。
最后一轮 for 循环才是在比对所有分界线情况,并找出最大值。
let sum = -Infinity
for (let i = L - 1; i < length - M; i++) {
sum = Math.max(sum, leftMax[i] + rightMax[i + 1]);
}
return sum
复制代码
函数 split_and_find_max_sum
需要调用两次,原因是两个子数组长度可能不同,谁在线左边,谁在线右边,需要分情况讨论。