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.
给出一个数组,在数组中找到3个不重叠的子数组,每个子数组长度为k,使3个子数组的和最大,返回三个子数组的起始index
思路:
可分解成几个小问题,
第一步,分解问题,因为子数组的长度是k,那么中间子数组的起始index只能从k开始,到n-2k为止。如果知道了中间子数组的位置,左右两边子数组的范围也就知道了,这个时候如果知道了左右两边最大和的起始index,就相当于知道了3个子数组的起始index,问题得解。
对每一个中间子数组的位置,要能迅速得知左右两边子数组的最大和的起始index,就需要提前保存好每个范围内最大和对应的起始index,这就用到DP
而且如果已知起始index,因为子数组长度为k,就会知道结尾index,求这段长度内数组元素的和,会想到利用积分数组,即累加和数组
所以这道题用了(1)积分数组以方便求任意一段子数组的元素和,(2)DP用于保存最大和的起始index,及(3)slide window以遍历中间数组的位置
第二步,逐个解决小问题
(1) 先求一个积分数组。为了方便求前k个元素的和(需要用sum[k-1]-sum[-1],为了不考虑-1这个边界),在sum前面补一个0
(2) DP保存左右两边子数组最大和的起始index,先从左到右遍历,0~k-1这段不用考虑了,起始index都是0,从k开始到n-k+1,已知起始index,结尾index也就知道了,利用积分数组算这段长度内元素的和,一旦大于前面一个就更新保存index,否则就还是上一个保存的index
同理右边,从右到左遍历,从n-k到0,但是因为同样的和要保留较小的index,这里>=前一个值时就更新index
(3) 遍历中间数组的起始index,每个起始index都会对应左右两边子数组的最大和,把它们仨加起来,每遍历一个更新最大值和对应的3个子数组的起始index
参考方法
public int[] maxSumOfThreeSubarrays(int[] nums, int k) {
if(nums == null || nums.length == 0 || k < 1) {
return new int[]{};
}
int n = nums.length;
int[] sum = new int[n+1];
int[] left = new int[n];
int[] right = new int[n];
int total = 0;
int[] result = new int[3];
for(int i = 1; i <= n; i++) {
sum[i] = sum[i-1] + nums[i-1];
}
total = 0;
for(int i = k-1; i < n; i++) {
int tmp = sum[i+1] - sum[i+1-k];
if(tmp > total) {
total = tmp;
left[i] = i-k+1;
} else {
left[i] = left[i-1];
}
}
total = 0;
for(int i = n-k; i >= 0; i--) {
int tmp = sum[i+k] - sum[i];
if(tmp >= total) {
total = tmp;
right[i] = i;
} else {
right[i] = right[i+1];
}
}
total = 0;
for(int i = k; i < n-2*k+1; i++) {
int tmp = sum[left[i-1]+k] - sum[left[i-1]] + sum[i+k] - sum[i]
+ sum[right[i+k] + k] - sum[right[i+k]];
if(tmp > total) {
total = tmp;
result[0] = left[i-1];
result[1] = i;
result[2] = right[i+k];
}
}
return result;
}