Question
In a given array nums of positive integers, find three non-overlapping subarrays with maximum sum.Each subarray will be of size k, and we want to maximize the sum of all 3*k entries.
Return the result as a list of indices representing the starting position of each interval (0-indexed). If there are multiple answers, return the lexicographically smallest one.
Example:
Input: [1,2,1,2,6,7,5,1], 2
Output: [0, 3, 5]
Explanation: Subarrays [1, 2], [2, 6], [7, 5] correspond to the starting indices [0, 3, 5].
We could have also taken [2, 1], but an answer of [1, 3, 5] would be lexicographically larger.
Note:- nums.length will be between 1 and 20000.
- nums[i] will be between 1 and 65535.
- k will be between 1 and floor(nums.length / 3).
Analysis
题目的要求是找到3个长度为k的互补重叠的子数组,使得总和最大。从给出的例子可以看出,单个子数组的和不一定是和最大的子数组,只要3个的总和最大就可以。所以这个题目不应该从和的角度考虑,也就是说先计算出所有的长度为k的子数组的和然后从最大值开始进行组合的方法是费时费力的,不应该往这个思路去考虑。
一个思路是从3的角度考虑。一般来说,
- 对于要找到1个最值的题目,可以用二分法的思路思考;
- 对于要找到2个数/子数组的题目,可以用两个指向首尾的指针相向移动,在线性的时间内求解;
- 对于要找到3个数/子数组的题目,可以固定其中一个指针,另外两个指针相向移动,在O(n^2)的时间内求解。
Solution 1
这个解答是LeetCode官方给出的一个解,实现是Python代码。注意其中用到了几个提速的技巧:- 预处理,先计算出了所有长度为k的子数组的和,免去不必要的重复计算,结果保存在W中。
- 然后在线性的时间内求解前i个子数组和的最大和与后i个子数组的最大值,分别保存在left和right中。
- 最后求解时,只需要固定中间的子数组的起点,然后从W中取中间子数组的和,从left和right中分别取第一个和第三个子数组的可能的最大和,加起来即固定中间的子数组的情况下,可能的最大和。
class Solution(object):
def maxSumOfThreeSubarrays(self, nums, K):
W = [] #array of sums of windows
sum_ = 0
for i, x in enumerate(nums):
sum_ += x
if i >= K: sum_ -= nums[i-K]
if i >= K-1: W.append(sum_)
left = [0] * len(W)
best = 0
for i in range(len(W)):
if W[i] > W[best]:
best = i
left[i] = best
right = [0] * len(W)
best = len(W) - 1
for i in range(len(W) - 1, -1, -1):
if W[i] >= W[best]:
best = i
right[i] = best
ans = None
for j in xrange(K, len(W) - K):
i, k = left[j-K], right[j+K]
if ans is None or (W[i] + W[j] + W[k] >
W[ans[0]] + W[ans[1]] + W[ans[2]]):
ans = i, j, k
return ans
Solution 2
但是这个方法并不是最快的算法。还有一个很“奇技淫巧”的算法,只需要一遍遍历就可以求解的。基本思想是归纳,思路如下:- 假设当前位置是i,已经知道了前i-2k个数中第一个长度为k的子数组最大和,设为max1,以及前i-k个数中前两个长度为k的子数组的最大和,设为max2,以及前i个数中所有三个长度为k的子数组的最大和,设为max3;
- 那么在位置i+1,只需要拿起始位置为i-2k+1的子数组和max1比较,就可以求得前i-2k+1个数中第一个长度为k的子数组的最大和;同理,比较和更新max2和max3;
- 起始状态是i=2k,此时max1为起始位置为0的长度为k的子数组的和;max2为前2k个数的和,max3为前3k个数的和;
- 依次递推,当计算到i=nums.size()-k的时候,就可以得到最终答案了。
class Solution {
public:
vector<int> maxSumOfThreeSubarrays(vector<int>& nums, int k)
{
vector<int> id0(1, -1);
vector<int> id1(2, -1);
vector<int> id2(3, -1);
vector<int> sumopt(3, -1);
vector<int> sumnums;
int sum = 0;
for(int i = 0; i < k; ++i)
{
sum += nums[i];
}
sumnums.push_back(sum);
for(int i = 0; i < nums.size() - k; ++i)
{
sum += nums[i + k] - nums[i];
sumnums.push_back(sum);
}
sumopt[0] = sumnums[0];
sumopt[1] = sumnums[k] + sumnums[0];
sumopt[2] = sumnums[k + k] + sumopt[1];
id0[0] = 0;
id1[0] = 0; id1[1] = k;
id2[0] = 0; id2[1] = k; id2[2] = k * 2;
for(int i = k * 2; i < sumnums.size(); ++i)
{
if(sumnums[i - k * 2] > sumopt[0])
{
sumopt[0] = sumnums[i - k * 2];
id0[0] = i - k * 2;
}
if(sumnums[i - k] + sumopt[0] > sumopt[1])
{
sumopt[1] = sumnums[i - k] + sumopt[0];
id1[0] = id0[0]; id1[1] = i - k;
}
if(sumnums[i] + sumopt[1] > sumopt[2])
{
sumopt[2] = sumnums[i] + sumopt[1];
id2[0] = id1[0]; id2[1] = id1[1]; id2[2] = i;
}
}
return id2;
}
};