给定数组arr和k,求3个不重叠的长为k的子数组的累加和之和最大值是多少?
提示:这个题不是数组三连问题
本题的两个重要知识点:
(1)数组arr中必须以i位置结尾的子数组,其最大累加和是多少?
https://blog.csdn.net/weixin_46838716/article/details/124375762?spm=1001.2014.3001.5502
(1)又是(2)的基础:
(2)数组arr的0–i范围上任选一个子数组的最大累加和是多少?
https://blog.csdn.net/weixin_46838716/article/details/124377952?spm=1001.2014.3001.5502
题目
给定数组arr和k,寻找3个不重叠的子数组,每个子数组的长为k,每个子数组累加和之和的最大值是多少?
一、审题
示例:arr=1,2,1,2,6,7,5,1
k=2
找3个长为2的子数组
他们各自累加和最大
再求和
二、解题
不重叠,找3个子数组,显然是要枚举的
但是不能每次枚举,都去单独求每个子数组内部的累加和
显然,我们又遇到了一个范围上求连续k长度的子数组累加和,所以必然要用前缀和数组加速
比如
L–R内的累加和是多少?
如果preSum[i]代表[0,i)上的累加和,注意,左闭右开,这是经常用到的,不包含i位置,故需要N+1长度
这样的话,我们可以快速拿到L–R内的累加和
这么思考本题:也是本题的解题大流程:
要找3个子数组,我们以中间那个数组为基准,枚举所有可能的情况:
(1)不妨设中间k长度的子数组和为part2,它占据的位置是i–i+k-1
(2)则0–i-1范围上咱可以任意找一个子数组(长度为k),其累加和最大为part1;
(3)同理,在i+k–N-1范围上,我们可以任意找一个子数组(长度为k),其累加和最大为part3;
通过枚举part2的位置i,至少从k开始枚举,最后一个位置是,N-2k
为啥最后一个位置N-2k?
因为,part3占据k长度,part2也要k长度,就长2k
N-2k–N-1就是2k长度
每次枚举一个位置的话,我们只需要枚举o(n)的复杂度
而每次枚举,我们希望能以o(1)速度拿到part1,part2,part3
刚刚说过,前缀累加和,可以快速拿到part2:sum[i+k]-sum[i]=sum[i–i+k-1]
那么如何快速拿到part1和part3呢?
——先来看如何快速拿到part1
之前讲过的基础知识:
数组arr的0–i范围上任选一个子数组的最大累加和是多少?
https://blog.csdn.net/weixin_46838716/article/details/124377952?spm=1001.2014.3001.5502
看看,part1可不就是找0–i位置上找任意一个子数组,求其最大累加和,这里要注意,唯一的区别就是:
本题中我们的任意子数组,长度必须是k。【这是限定条件】
这里啊,我们一样,讨论长为k的子数组的最大累加和,是不是以i结尾?
(1)是以i结尾,那不扯只含i,或者既包含i又包含前面的元素,咱现在就是i及前面k个数的累加和;
(2)不是以i结尾,那就是说之前求过的某个最大值就行
故,定义dp[i]是0–i范围内,任意长度为k的子数组的最大累加和;
咋求呢?这样:
先整一个前缀累加和:sumFromLeft,从左往右的前缀累加和【上面说得很清楚】
然后,定义个数组dpLeft也就是这里的dp
每一个dpLeft[i]咋算呢?
dpLeft[i] = Math.max(dpLeft[i - 1], sumFromLeft[i + 1] - sumFromLeft[i - k + 1]);
怎么理解,dpLeft[i - 1]就是不以i结尾的子数组,前面0–i-1范围上随便找一个即可
跟是谁比呢?自然是必须以i结尾的那个子数组
sumFromLeft[i + 1] - sumFromLeft[i - k + 1])就是i–i+k范围内的长为k的子数组累加和
这样的话dpLeft就准备好了
到时候一旦调:dpLeft[i],就o(1)速度拿到0–i范围内,任意长度为k的子数组的最大累加和。
——同理,咱们怎么如何快速拿到part3 ?
故,定义dpRight[i]是i–N-1范围内,任意长度为k的子数组的最大累加和;
咋求呢?这样:
先整一个前缀累加和:sumFromRight,从右往左的前缀累加和【对比part1就知道,上面说得很清楚】
如果i从0开始算,则下标也很好推:
sumFromRight[N - 1 - i] = sumFromRight[N - i] + arr[i];//右边加过来
然后,定义个数组dpRight也就是这里的dp
每一个dpRight[i]咋算呢?
dpRight[i] = Math.max(dpRight[i + 1], sumFromRight[i - 1] - sumFromRight[i + k - 1]);
怎么理解,dpRight[i + 1]就是不以i开头的子数组,后面,i+1–N-1范围上随便找一个即可
跟是谁比呢?自然是必须以i开头的那个子数组
sumFromRight[i - 1] - sumFromLeft[i - k + 1])就是i–i+k范围内的长为k的子数组累加和
这样的话dpRight就准备好了
到时候一旦调:dpRight[i],就o(1)速度拿到i–N-1范围内,任意长度为k的子数组的最大累加和。
有了part2
有了dpLeft数组
有了dpRight数组
咱下面就可以枚举part2的所有情况了
每次三个累加和再求和,更新max即可,也就是本题的解法:
public static int maxSumOf3SubArray(int[] arr, int k){
if (arr == null || arr.length < 3 * k) return 0;
//i,长度为k的子数组累加和是多少????part2
int N = arr.length;
int[] sumFromLeft = new int[N + 1];
int[] sumFromRight = new int[N + 1];
sumFromLeft[0] = 0;
sumFromLeft[N] = 0;//没开始之前都是0
for (int i = 0; i < N; i++) {
sumFromLeft[i + 1] = sumFromLeft[i] + arr[i];
sumFromRight[N - 1 - i] = sumFromRight[N - i] + arr[i];//右边加过来
}
//首先解决一下0--i位置上的任意子数组,长度为k,它的最大累加和是多少????dpLeft
int[] dpLeft = new int[N];
for (int i = 0; i < k; i++) {
dpLeft[k - 1] += arr[i];//连续k个,只需要记录k-1位置
}
for (int i = k; i < N; i++) {
dpLeft[i] = Math.max(dpLeft[i - 1], sumFromLeft[i + 1] - sumFromLeft[i - k + 1]);
// i开始往前数k个
}
//首先解决一下i--N-1位置上的任意子数组,长度为k,它的最大累加和是多少????dpRight
int[] dpRight = new int[N];
for (int i = N - 1; i > N - 1 - k; i--) {
dpRight[N - k] += arr[i];//连续加k次,只需要记录N-k这个位置
}
for (int i = N - 1 - k; i > 0; i--) {
dpRight[i] = Math.max(dpRight[i + 1], sumFromRight[i - 1] - sumFromRight[i + k - 1]);
}
int max = 0;
//枚举中间数组-----大流程核心思想
for (int i = k; i <= N - 2*k; i++) {
//i是中间区间的起点:
// 得预留左边0--k-1,所以从k开始
//右边还得留至少k长度,中间自己长为k,所以右边至少起点最大是N-2k[坐标是0--N-1],恰好能取到N-2k
//坐标能换算清楚
int part2 = sumFromLeft[i + k] - sumFromLeft[i];//中间,包含位置i
int part1 = dpLeft[i - 1];//左边这部分中最大累加和
int part3 = dpRight[i + k - 1];//右边那部分中最大累加和--coding清楚坐标位置
max = Math.max(max, part1 + part2 + part3);
}
return max;
}
public static void test(){
int[] arr = {1,2,1,2,6,7,5,1};
int k = 2;
System.out.println(maxSumOf3SubArray(arr, k));
}
总结
提示:重要经验:
1)熟悉2大数组累加和的基础,对于理解和解决本题,至关重要
2)用预设前缀累加和数组,可以快速拿到L–R内的累加和
3)准备好0–i范围内任意长度为k的子数组的最大累加和数组;
4)准备好i–N-1范围内任意长度为k的子数组最大累加和数组;
可以快速帮助破解本题