Hard
题目描述
给定一个整数数组 nums 和一个整数 k,找出三个长度为 k 的非重叠子数组,使得它们的和最大,并返回这三个子数组的起始位置。
返回结果应该是一个包含三个元素的数组,表示每个子数组的起始索引(0索引)。如果有多个答案,返回字典序最小的一个。
示例 1:
输入: nums = [1,2,1,2,6,7,5,1], k = 2
输出: [0,3,5]
解释: 子数组 [1, 2], [2, 6], [7, 5] 对应的起始索引为 [0, 3, 5]。
我们也可以取 [2, 1],但答案 [1, 3, 5] 在字典序上更大。
示例 2:
输入: nums = [1,2,1,2,1,2,1,2,1], k = 2
输出: [0,2,4]
约束条件:
1 <= nums.length <= 2 * 10^41 <= nums[i] < 2^161 <= k <= floor(nums.length / 3)
解题思路
解题思路
这是一道经典的动态规划问题,需要找到三个不重叠的长度为 k 的子数组,使其和最大。
核心思路:
- 滑动窗口预处理:首先计算所有长度为 k 的子数组的和
- 分段动态规划:将问题分为三段来考虑
- 左段:记录从左开始到当前位置的最大子数组位置
- 右段:记录从当前位置到右端的最大子数组位置
- 中段:枚举中间子数组的位置,结合左右段信息
具体步骤:
- 计算每个长度为 k 的子数组的和
- 构建
left数组:left[i]表示在区间[0, i]内最大子数组的起始位置 - 构建
right数组:right[i]表示在区间[i, n-k]内最大子数组的起始位置 - 枚举中间子数组的起始位置
mid,计算left[mid-k] + sums[mid] + right[mid+k]的最大值
为了保证字典序最小,在和相等时选择索引较小的位置。这种方法时间复杂度为 O(n),空间复杂度为 O(n)。
代码实现
class Solution {
public:
vector<int> maxSumOfThreeSubarrays(vector<int>& nums, int k) {
int n = nums.size();
vector<int> sums(n - k + 1);
// 计算所有长度为k的子数组的和
int sum = 0;
for (int i = 0; i < k; i++) {
sum += nums[i];
}
sums[0] = sum;
for (int i = 1; i < n - k + 1; i++) {
sum = sum - nums[i - 1] + nums[i + k - 1];
sums[i] = sum;
}
// left[i]表示在[0, i]范围内最大子数组的起始位置
vector<int> left(n - k + 1);
int maxIdx = 0;
for (int i = 0; i < n - k + 1; i++) {
if (sums[i] > sums[maxIdx]) {
maxIdx = i;
}
left[i] = maxIdx;
}
// right[i]表示在[i, n-k]范围内最大子数组的起始位置
vector<int> right(n - k + 1);
maxIdx = n - k;
for (int i = n - k; i >= 0; i--) {
if (sums[i] >= sums[maxIdx]) {
maxIdx = i;
}
right[i] = maxIdx;
}
// 枚举中间子数组的位置
vector<int> result(3);
int maxSum = 0;
for (int mid = k; mid <= n - 2 * k; mid++) {
int l = left[mid - k];
int r = right[mid + k];
int totalSum = sums[l] + sums[mid] + sums[r];
if (totalSum > maxSum) {
maxSum = totalSum;
result[0] = l;
result[1] = mid;
result[2] = r;
}
}
return result;
}
};
class Solution:
def maxSumOfThreeSubarrays(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
# 计算所有长度为k的子数组的和
sums = []
window_sum = sum(nums[:k])
sums.append(window_sum)
for i in range(1, n - k + 1):
window_sum = window_sum - nums[i - 1] + nums[i + k - 1]
sums.append(window_sum)
# left[i]表示在[0, i]范围内最大子数组的起始位置
left = [0] * (n - k + 1)
max_idx = 0
for i in range(n - k + 1):
if sums[i] > sums[max_idx]:
max_idx = i
left[i] = max_idx
# right[i]表示在[i, n-k]范围内最大子数组的起始位置
right = [0] * (n - k + 1)
max_idx = n - k
for i in range(n - k, -1, -1):
if sums[i] >= sums[max_idx]:
max_idx = i
right[i] = max_idx
# 枚举中间子数组的位置
max_sum = 0
result = [0, 0, 0]
for mid in range(k, n - 2 * k + 1):
l = left[mid - k]
r = right[mid + k]
total_sum = sums[l] + sums[mid] + sums[r]
if total_sum > max_sum:
max_sum = total_sum
result = [l, mid, r]
return result
public class Solution {
public int[] MaxSumOfThreeSubarrays(int[] nums, int k) {
int n = nums.Length;
int[] sums = new int[n - k + 1];
// 计算所有长度为k的子数组的和
int sum = 0;
for (int i = 0; i < k; i++) {
sum += nums[i];
}
sums[0] = sum;
for (int i = 1; i < n - k + 1; i++) {
sum = sum - nums[i - 1] + nums[i + k - 1];
sums[i] = sum;
}
// left[i]表示在[0, i]范围内最大子数组的起始位置
int[] left = new int[n - k + 1];
int maxIdx = 0;
for (int i = 0; i < n - k + 1; i++) {
if (sums[i] > sums[maxIdx]) {
maxIdx = i;
}
left[i] = maxIdx;
}
// right[i]表示在[i, n-k]范围内最大子数组的起始位置
int[] right = new int[n - k + 1];
maxIdx = n - k;
for (int i = n - k; i >= 0; i--) {
if (sums[i] >= sums[maxIdx]) {
maxIdx = i;
}
right[i] = maxIdx;
}
// 枚举中间子数组的位置
int[] result = new int[3];
int maxSum = 0;
for (int mid = k; mid <= n - 2 * k; mid++) {
int l = left[mid - k];
int r = right[mid + k];
int totalSum = sums[l] + sums[mid] + sums[r];
if (totalSum > maxSum) {
maxSum = totalSum;
result[0] = l;
result[1] = mid;
result[2] = r;
}
}
return result;
}
}
var maxSumOfThreeSubarrays = function(nums, k) {
const n = nums.length;
const sums = [];
// 计算所有长度为k的子数组的和
let sum = 0;
for (let i = 0; i < k; i++) {
sum += nums[i];
}
sums.push(sum);
for (let i = 1; i < n - k + 1; i++) {
sum = sum - nums[i - 1] + nums[i + k - 1];
sums.push(sum);
}
// left[i]表示在[0, i]范围内最大子数组的起始位置
const left = new Array(n - k + 1);
let maxIdx = 0;
for (let i = 0; i < n - k + 1; i++) {
if (sums[i] > sums[maxIdx]) {
maxIdx = i;
}
left[i] = maxIdx;
}
// right[i]表示在[i, n-k]范围内最大子数组的起始位置
const right = new Array(n - k + 1);
maxIdx = n - k;
for (let i = n - k; i >= 0; i--) {
if (sums[i] >= sums[maxIdx]) {
maxIdx = i;
}
right[i] = maxIdx;
}
// 枚举中间子数组的位置
const result = [0, 0, 0];
let maxSum = 0;
for (let mid = k; mid <= n - 2 * k; mid++) {
const l = left[mid - k];
const r = right[mid + k];
const totalSum = sums[l] + sums[mid] + sums[r];
if (totalSum > maxSum) {
maxSum = totalSum;
result[0] = l;
result[1] = mid;
result[2] = r;
}
}
return result;
};
复杂度分析
| 复杂度类型 | 值 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 需要遍历数组计算子数组和,构建left和right数组,以及枚举中间位置 |
| 空间复杂度 | O(n) | 需要额外的数组存储子数组和、left数组和right数组 |