1. 问题背景与核心挑战
这道LeetCode 689题要求我们在给定数组中找出三个不重叠的子数组,每个子数组长度为k,使得这三个子数组的和之和最大。这个问题在实际应用中有着重要意义,比如在信号处理中寻找多个最优信号段,或者在金融分析中识别多个高收益区间。
核心难点在于:
- 需要同时考虑三个子数组的组合
- 子数组之间不能重叠
- 需要找到全局最优解而非局部最优
- 时间复杂度需要控制在合理范围内(O(n))
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 暴力解法与优化思路
最直观的暴力解法是三重循环遍历所有可能的子数组组合,时间复杂度高达O(n^3),这在n较大时完全不可行。我们需要通过以下优化思路来降低复杂度:
2.1 滑动窗口预处理
首先对数组进行预处理,计算所有长度为k的子数组和:
java复制int[] windowSums = new int[n - k + 1];
int currentSum = 0;
for (int i = 0; i < n; i++) {
currentSum += nums[i];
if (i >= k) currentSum -= nums[i - k];
if (i >= k - 1) windowSums[i - k + 1] = currentSum;
}
这样我们将问题转化为在windowSums数组中寻找三个不重叠的索引,使它们的和最大。
2.2 动态规划状态定义
定义两个辅助数组:
- left[i]:表示在[0,i]范围内,长度为k的子数组和的最大值的起始索引
- right[i]:表示在[i,n-1]范围内,长度为k的子数组和的最大值的起始索引
这样可以将问题分解为:对于每个可能的中间子数组位置j,计算left[j-k] + windowSums[j] + right[j+k]的最大值。
3. 完整算法实现
3.1 预处理阶段
java复制int n = nums.length;
int[] windowSums = new int[n - k + 1];
int currentSum = 0;
for (int i = 0; i < n; i++) {
currentSum += nums[i];
if (i >= k) currentSum -= nums[i - k];
if (i >= k - 1) windowSums[i - k + 1] = currentSum;
}
3.2 构建left和right数组
java复制int[] left = new int[windowSums.length];
int best = 0;
for (int i = 0; i < windowSums.length; i++) {
if (windowSums[i] > windowSums[best]) best = i;
left[i] = best;
}
int[] right = new int[windowSums.length];
best = windowSums.length - 1;
for (int i = windowSums.length - 1; i >= 0; i--) {
if (windowSums[i] >= windowSums[best]) best = i;
right[i] = best;
}
3.3 寻找最优解
java复制int[] result = new int[3];
int maxSum = 0;
for (int j = k; j < windowSums.length - k; j++) {
int i = left[j - k], l = right[j + k];
int currentTotal = windowSums[i] + windowSums[j] + windowSums[l];
if (currentTotal > maxSum) {
maxSum = currentTotal;
result[0] = i;
result[1] = j;
result[2] = l;
}
}
return result;
4. 算法复杂度分析
-
时间复杂度:O(n)
- 预处理阶段:O(n)
- 构建left和right数组:各O(n)
- 寻找最优解:O(n)
-
空间复杂度:O(n)
- 需要存储windowSums、left和right数组
5. 边界条件与注意事项
5.1 输入验证
java复制if (nums == null || nums.length < 3 * k)
throw new IllegalArgumentException("Input array is too short");
5.2 相等和的处理
当存在多个子数组和相同的情况时,题目要求返回字典序最小的解。这在我们的实现中已经通过left和right数组的处理方式自动满足:
- left数组在更新时使用>比较
- right数组在更新时使用>=比较
5.3 索引范围检查
在寻找最优解时,中间子数组的索引j需要满足:
java复制j >= k && j < windowSums.length - k
确保左右都有足够的空间放置另外两个子数组。
6. 测试用例设计
6.1 基础测试用例
java复制int[] nums = {1,2,1,2,6,7,5,1};
int k = 2;
// 预期输出: [0,3,5]
6.2 边界测试用例
java复制// 最小长度输入
int[] nums = {1,1,1,1,1,1};
int k = 1;
// 预期输出: [0,1,2]
// 所有元素相同
int[] nums = {5,5,5,5,5,5,5};
int k = 2;
// 预期输出: [0,2,4]
6.3 随机生成测试
java复制Random rand = new Random();
int[] nums = new int[10000];
for (int i = 0; i < nums.length; i++) {
nums[i] = rand.nextInt(100);
}
int k = 100;
// 验证算法在大数据量下的表现
7. 算法优化与变种
7.1 空间优化
可以优化掉left和right数组,改为在遍历时实时维护左右的最大值,将空间复杂度降至O(1)。
7.2 扩展到m个子数组
该算法可以推广到寻找m个不重叠子数组的最大和,时间复杂度为O(m*n),空间复杂度为O(n)。
7.3 重叠子数组问题
如果允许子数组有有限重叠,可以调整索引检查条件,核心思路保持不变。
8. 实际应用场景
- 金融分析:寻找多个最佳投资时段
- 信号处理:识别多个重要信号区间
- 基因组学:定位DNA序列中的多个重要片段
- 视频处理:提取多个关键帧区间
9. 常见错误与调试技巧
9.1 索引越界
常见于:
- 忘记检查输入数组长度
- 计算windowSums数组长度时出错
- 遍历j的范围不正确
调试方法:打印中间数组长度和索引值
9.2 结果不正确
可能原因:
- left/right数组更新逻辑错误
- 最大值比较时忽略了相等情况
- 字典序处理不当
调试方法:用小测试用例逐步跟踪变量值
9.3 性能问题
当n很大时:
- 避免使用过大的k值
- 考虑空间优化版本
- 检查是否有不必要的计算
10. 与其他算法的对比
10.1 与单子数组最大和对比
单子数组最大和可以使用Kadane算法O(n)解决,而本题是其扩展,需要更复杂的预处理和状态维护。
10.2 与背包问题对比
虽然都有"选择"的概念,但背包问题是组合优化,而本题是区间选择,解法思路不同。
10.3 与滑动窗口最大值对比
滑动窗口最大值使用单调队列,而本题需要同时考虑多个窗口的协同。
