题目简述:
给你一个整数数组
nums和两个整数firstLen和secondLen,请你找出并返回两个无重叠 子数组 中元素的最大和*,*长度分别为firstLen和secondLen。长度为
firstLen的子数组可以出现在长为secondLen的子数组之前或之后,但二者必须是无重叠。子数组是数组的一个 连续 部分。
提示:
1 <= firstLen, secondLen <= 10002 <= firstLen + secondLen <= 1000firstLen + secondLen <= nums.length <= 10000 <= nums[i] <= 1000
题目链接:1031. 两个无重叠子数组的最大和
对于子数组和相关的问题,首先考虑前缀和数组 presum。
我们将长为 firstLen 的子数组记为 a、将长为 secondLen 的子数组记为 b。然后,不妨先假设 a 位于 b 的左侧,则当 b 是固定的时,最大和的 a 是能被确定的。这样,我们就将问题拆解为了若干个最大和子数组的子问题。
对于一个数组,要求解其和最大的定长子数组需要 fixedLenPresum。构建前缀和数组的时间复杂度为 fixedLenPresum[i] 的值,则计算 fixedLenPresum[j] 只需要付出
这个式子可以视为一种很简单的 DP,这是为什么力扣上这道题有动态规划的标签。
于是,计算出 fixedLenPresum 序列只需要付出
现在,我们再遍历索引,考虑每个点作为 b 终点的可能性,通过 fixedLenPresum,能够直接查表得到 b 起点左侧的长为 firstLen 的子数组的最大和。这样,经过一次完整的遍历,就能得到 a 位于 b 左侧的题设所要求的两个无重叠子数组的最大和。
若 b 位于 a 的左侧,同理,重复上述操作即可。
这样,我们就能在
但我不理解为什么力扣上本问题限定的数据范围是 firstLen + secondLen <= nums.length <= 1000,这差点引导我思考并设计
总的来看,我们的算法属于滑动窗口(存在定长窗口的双指针算法),结合了前缀和数组。
算法的时间复杂度为
class Solution {
public int maxSumTwoNoOverlap(int[] nums, int firstLen, int secondLen) {
int n = nums.length;
int[] presum = new int[n + 1];
for (int i = 0; i < n; i++) presum[i + 1] = presum[i] + nums[i];
int[] fixedFirstLenPresum = new int[n];
int[] fixedSecondLenPresum = new int[n];
int res = 0;
// init
fixedFirstLenPresum[firstLen] = presum[firstLen];
fixedSecondLenPresum[secondLen] = presum[secondLen];
for (int i = firstLen; i <= n - secondLen; i++) {
fixedFirstLenPresum[i] = Math.max(fixedFirstLenPresum[i - 1], presum[i] - presum[i - firstLen]);
res = Math.max(res, fixedFirstLenPresum[i] + presum[i + secondLen] - presum[i]);
}
for (int i = secondLen; i <= n - firstLen; i++) {
fixedSecondLenPresum[i] = Math.max(fixedSecondLenPresum[i - 1], presum[i] - presum[i - secondLen]);
res = Math.max(res, fixedSecondLenPresum[i] + presum[i + firstLen] - presum[i]);
}
return res;
}
}