689. 三个无重叠子数组的最大和
给定数组 nums 由正整数组成,找到三个互不重叠的子数组的最大和。
每个子数组的长度为k,我们要使这3*k个项的和最大化。
返回每个区间起始索引的列表(索引从 0 开始)。如果有多个结果,返回字典序最小的一个。
示例
输入: [1,2,1,2,6,7,5,1], 2
输出: [0, 3, 5]
解释: 子数组 [1, 2], [2, 6], [7, 5] 对应的起始索引为 [0, 3, 5]。
我们也可以取 [2, 1], 但是结果 [1, 3, 5] 在字典序上更大。
注意:
- nums.length的范围在[1, 20000]之间。
- nums[i]的范围在[1, 65535]之间。
- k的范围在[1, floor(nums.length / 3)]之间。
题解
- 假设nums的长度为len,求出nums数组所有连续k个子序列的和的数组sums,已知nums的长度为len,sums的长度则为len-k+1,用t表示。
- 那么题目就可以转化为求sums数组,选3个数,这三个数必须满足,相邻间隔不小于k,当和最大时,脚标最小的三个数。
- 定义dp数组,dp[i][j]表示的是从[0, i]的范围内,选择j个数得到的最大和(j个数的相邻间隔为k)
- 定义path数组,path[i][j]表示的是当得到dp[i][j]的时候,选择的最后一个数的脚标
定义dp数组的状态转移方程
sums[i]分为选择 和 不选择
当选择sums[i] 时 ,dp[i][j] = dp[i-k][j-1] + sums[i];
当不选择sums[i] 时, dp[i][j] = dp[i-1][j];
最终求最大值: dp[i][j] = max{dp[i-k][j-1] + sums[i], dp[i-1][j]}
初始化dp数组需要注意的是
要在sums数组中选择j个数,并且满足相邻的数间隔不小于k,那么sums的长度必须满足t > (j-1)*k,由于i代表的是脚标,所以只有在i >= (j-1)*k的时候,dp数组才有意义
边界问题:当k=1时,比较特殊,可以单独初始化,因为k=1时,哪怕sums只有一个数,也可以选择,但是如果k>1时,比如k=2,当sums有2个数的时候,其实是无法选择的。
以 [1,2,1,2,6,7,5,1], 2 输入为例,
sums数组为:
[3, 3, 3, 8, 13, 12, 6]
得到的dp数组和path数组分别为:
dp | 0 | 1 | 2 | 3 |
---|---|---|---|---|
0 | 0 | 3 | 0 | 0 |
1 | 0 | 3 | 0 | 0 |
2 | 0 | 3 | 6 | 0 |
3 | 0 | 8 | 11 | 0 |
4 | 0 | 13 | 16 | 19 |
5 | 0 | 13 | 20 | 23 |
6 | 0 | 13 | 20 | 23 |
path | 0 | 1 | 2 | 3 |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 2 | 0 |
3 | 0 | 3 | 3 | 0 |
4 | 0 | 4 | 4 | 4 |
5 | 0 | 4 | 5 | 5 |
6 | 0 | 4 | 5 | 5 |
最后得到path,那么path[t-1][3]就是从[0, t-1]的范围内,选择3个数和最大时,最后一个数的脚标,用res[2]来表示,那么前一个的脚标一定是[0, res[2]-k]的范围内,选择2个数和最大时,最后一个数的脚标,依次类推,得到第一个脚标。
代码如下:
class Solution {
public int[] maxSumOfThreeSubarrays(int[] nums, int k) {
int len = nums.length;
int t = len-k+1;
int[] sums = new int[t];
int[] res = new int[3];
for(int i=0; i<k; i++){
sums[0] += nums[i];
}
for(int i=1;i<t;i++){
sums[i] = sums[i-1]-nums[i-1]+nums[i+k-1];
}
int[][] dp = new int[t][4];
int[][] path = new int[t][4];
dp[0][1] = sums[0];
path[0][1] = 0;
for(int i = 1;i<t;i++){
dp[i][1] = Math.max(dp[i-1][1], sums[i]);
path[i][1] = sums[i] > dp[i-1][1] ? i : path[i-1][1];
}
for(int i=0;i<t;i++){
for(int j=2;j<4;j++){
if (i >= (j-1)*k){
int select = dp[i-k][j-1] + sums[i];
int notSelect = dp[i-1][j];
dp[i][j] = Math.max(select, notSelect);
path[i][j] = path[i-1][j];
if(select > notSelect){
path[i][j] = i;
}
}
}
}
res[2] = path[t-1][3];
res[1] = path[res[2]-k][2];
res[0] = path[res[1]-k][1];
return res;
}
该题还可以引申一下,把找3个子数组,引申到找n个子数组:
class Solution {
public int[] maxSumOfThreeSubarrays(int[] nums, int k) {
return maxSumOfNSubarrays(nums,k,3);
}
public int[] maxSumOfNSubarrays(int[] nums, int k, int n){
int len = nums.length;
int t = len-k+1;
int[] sums = new int[t];
int[] res = new int[n];
for(int i=0; i<k; i++){
sums[0] += nums[i];
}
for(int i=1;i<t;i++){
sums[i] = sums[i-1]-nums[i-1]+nums[i+k-1];
}
int[][] dp = new int[t][n+1];
int[][] path = new int[t][n+1];
dp[0][1] = sums[0];
path[0][1] = 0;
for(int i = 1;i<t;i++){
dp[i][1] = Math.max(dp[i-1][1], sums[i]);
path[i][1] = sums[i] > dp[i-1][1] ? i : path[i-1][1];
}
for(int i=0;i<t;i++){
for(int j=2;j<n+1;j++){
if (i >= (j-1)*k){
int select = dp[i-k][j-1] + sums[i];
int notSelect = dp[i-1][j];
dp[i][j] = Math.max(select, notSelect);
path[i][j] = path[i-1][j];
if(select > notSelect){
path[i][j] = i;
}
}
}
}
int row = t-1;
int col = n;
res[n-1] = path[row][col--];
for(int i=n-2; i>=0; i--){
res[i] = path[res[i+1]-k][col--];
}
return res;
}
}