Medium
题目描述
给你一个整数数组 nums 和两个整数 firstLen 和 secondLen,请你找出并返回两个非重叠子数组中元素的最大和,子数组长度分别为 firstLen 和 secondLen。
长度为 firstLen 的子数组可以出现在长度为 secondLen 的子数组之前或之后,但二者必须是不重叠的。
子数组是数组的一个连续部分。
示例 1:
输入:nums = [0,6,5,2,2,5,1,9,4], firstLen = 1, secondLen = 2
输出:20
解释:其中一种选择方案是长度为 1 的子数组 [9] 和长度为 2 的子数组 [6,5]。
示例 2:
输入:nums = [3,8,1,3,2,1,8,9,0], firstLen = 3, secondLen = 2
输出:29
解释:其中一种选择方案是长度为 3 的子数组 [3,8,1] 和长度为 2 的子数组 [8,9]。
示例 3:
输入:nums = [2,1,5,6,0,9,5,0,3,8], firstLen = 4, secondLen = 3
输出:31
解释:其中一种选择方案是长度为 4 的子数组 [5,6,0,9] 和长度为 3 的子数组 [0,3,8]。
提示:
1 <= firstLen, secondLen <= 10002 <= firstLen + secondLen <= 1000firstLen + secondLen <= nums.length <= 10000 <= nums[i] <= 1000
解题思路
解题思路
这道题要求找出两个不重叠子数组的最大和,可以用动态规划思想结合滑动窗口来解决。
核心思路是分两种情况考虑:
- 长度为
firstLen的子数组在前,长度为secondLen的子数组在后 - 长度为
secondLen的子数组在前,长度为firstLen的子数组在后
对于每种情况,我们可以:
- 使用滑动窗口计算所有可能的子数组和
- 维护当前位置之前最大的子数组和
- 对于当前位置的子数组,找到它之前的最佳子数组组合
具体实现时,我们定义一个辅助函数来处理"长度为L的子数组在前,长度为M的子数组在后"的情况。遍历所有可能的第二个子数组位置,同时维护第一个子数组的最大值,从而得到最优组合。
最终答案是两种情况的最大值。这种方法避免了暴力枚举所有可能的子数组组合,时间复杂度为O(n)。
(推荐解法):动态规划 + 滑动窗口,时间复杂度O(n),空间复杂度O(1)。
代码实现
class Solution {
public:
int maxSumTwoNoOverlap(vector<int>& nums, int firstLen, int secondLen) {
return max(helper(nums, firstLen, secondLen), helper(nums, secondLen, firstLen));
}
private:
int helper(vector<int>& nums, int L, int M) {
int n = nums.size();
// 计算第一个长度为L的子数组的和
int sumL = 0;
for (int i = 0; i < L; i++) {
sumL += nums[i];
}
// 计算第一个长度为M的子数组的和(从位置L开始)
int sumM = 0;
for (int i = L; i < L + M; i++) {
sumM += nums[i];
}
int maxL = sumL; // 当前位置之前长度为L的子数组的最大和
int result = maxL + sumM;
// 滑动窗口,同时维护maxL和sumM
for (int i = L + M; i < n; i++) {
// 更新长度为M的子数组和
sumM = sumM - nums[i - M] + nums[i];
// 更新长度为L的子数组和
sumL = sumL - nums[i - M - L] + nums[i - M];
// 更新maxL
maxL = max(maxL, sumL);
// 更新结果
result = max(result, maxL + sumM);
}
return result;
}
};
class Solution:
def maxSumTwoNoOverlap(self, nums: List[int], firstLen: int, secondLen: int) -> int:
def helper(L, M):
# L在前,M在后
n = len(nums)
# 计算第一个长度为L的子数组的和
sum_L = sum(nums[:L])
# 计算第一个长度为M的子数组的和(从位置L开始)
sum_M = sum(nums[L:L+M])
max_L = sum_L # 当前位置之前长度为L的子数组的最大和
result = max_L + sum_M
# 滑动窗口,同时维护max_L和sum_M
for i in range(L + M, n):
# 更新长度为M的子数组和
sum_M = sum_M - nums[i - M] + nums[i]
# 更新长度为L的子数组和
sum_L = sum_L - nums[i - M - L] + nums[i - M]
# 更新max_L
max_L = max(max_L, sum_L)
# 更新结果
result = max(result, max_L + sum_M)
return result
return max(helper(firstLen, secondLen), helper(secondLen, firstLen))
public class Solution {
public int MaxSumTwoNoOverlap(int[] nums, int firstLen, int secondLen) {
return Math.Max(Helper(nums, firstLen, secondLen), Helper(nums, secondLen, firstLen));
}
private int Helper(int[] nums, int L, int M) {
int n = nums.Length;
// 计算第一个长度为L的子数组的和
int sumL = 0;
for (int i = 0; i < L; i++) {
sumL += nums[i];
}
// 计算第一个长度为M的子数组的和(从位置L开始)
int sumM = 0;
for (int i = L; i < L + M; i++) {
sumM += nums[i];
}
int maxL = sumL; // 当前位置之前长度为L的子数组的最大和
int result = maxL + sumM;
// 滑动窗口,同时维护maxL和sumM
for (int i = L + M; i < n; i++) {
// 更新长度为M的子数组和
sumM = sumM - nums[i - M] + nums[i];
// 更新长度为L的子数组和
sumL = sumL - nums[i - M - L] + nums[i - M];
// 更新maxL
maxL = Math.Max(maxL, sumL);
// 更新结果
result = Math.Max(result, maxL + sumM);
}
return result;
}
}
var maxSumTwoNoOverlap = function(nums, firstLen, secondLen) {
const helper = (L, M) => {
const n = nums.length;
// 计算第一个长度为L的子数组的和
let sumL = 0;
for (let i = 0; i < L; i++) {
sumL += nums[i];
}
// 计算第一个长度为M的子数组的和(从位置L开始)
let sumM = 0;
for (let i = L; i < L + M; i++) {
sumM += nums[i];
}
let maxL = sumL; // 当前位置之前长度为L的子数组的最大和
let result = maxL + sumM;
// 滑动窗口,同时维护maxL和sumM
for (let i = L + M; i < n; i++) {
// 更新长度为M的子数组和
sumM = sumM - nums[i - M] + nums[i];
// 更新长度为L的子数组和
sumL = sumL - nums[i - M - L] + nums[i - M];
// 更新maxL
maxL = Math.max(maxL, sumL);
// 更新结果
result = Math.max(result, maxL + sumM);
}
return result;
};
return Math.max(helper(firstLen, secondLen), helper(secondLen, firstLen));
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 需要遍历数组两次,每次遍历为O(n) |
| 空间复杂度 | O(1) | 只使用了常数个额外变量 |