LC 689. 三个无重叠子数组的最大和
题目描述
这是 LeetCode 上的 689. 三个无重叠子数组的最大和 ,难度为 困难。
给你一个整数数组 nums
和一个整数 k
,找出三个长度为 k
、互不重叠、且 3 * k
项的和最大的子数组,并返回这三个子数组。
以下标的数组形式返回结果,数组中的每一项分别指示每个子数组的起始位置(下标从 $0$ 开始)。
如果有多个结果,返回字典序最小的一个。
示例 1:1
2
3
4
5
6输入: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:1
2
3输入:nums = [1,2,1,2,1,2,1,2,1], k = 2
输出:[0,2,4]
提示:
- $1 <= nums.length <= 2 \times 10^4$
- $1 <= nums[i] < 2^{16}$
- $1 <= k <= floor(nums.length / 3)$
前缀和 + 序列 DP
若不考虑输出方案,仅是求「三个无重叠子数组的最大和」的最大值。
只需要使用动态规划求解即可:定义 $f[i][j]$ 为考虑前 $i$ 个数,凑成无重叠子数组数量为 $j$ 个时的最大值。
最终答案为 $f[n - 1][3]$。
不失一般性的考虑 $f[i][j]$ 该如何计算(以最优方案是否包含 $nums[i]$ 进行讨论):
- 最优方案中包含 $num[i]$:由于这 $j$ 个无重叠,因此前面的 $j - 1$ 个子数组不能覆盖 $[i - k + 1, i]$。即只能在 $[0, i - k]$ 中选 $j - 1$ 个子数组。此时有:
其中求解 $\sum_{idx = i - k + 1}^{i} nums[idx]$ 部分可以使用「前缀和」优化
- 最优方案不包含 $num[i]$:当明确了 $nums[i]$ 对最优方案无贡献,此时问题等价于考虑前 $i - 1$ 个数,凑成 $j$ 个不重叠子数组的最大值。此时有:
最终 $f[i][j]$ 为上述两种方案中的最大值。
然后考虑「如何回溯出字典序最小的具体方案」,常规的回溯具体方案的做法是,从最终答案 $f[n - 1][3]$ 开始往回追溯。
利用 $f[n - 1][3]$ 仅由两个可能的节点($f[n - 2][3]$ 和 $f[n - 1 - k][2]$)转移而来,通过判断 $f[n - 1][3]$ 等于 $f[n - 2][3]$ 还是 $f[n - 1 - k][2] + \sum_{idx = n - k }^{n - 1} nums[idx]$ 来决定回溯点为何值。
但该做法只能确保回溯出字典序最大的方案是正确(当两个可能的前驱节点都能转移到 $f[i][j]$ 时优先采纳靠后的位置),而我们需要回溯出字典序最小的方案。
在上述解法的基础上,有两种「求解字典序最小具体方案」的做法:
将正序 DP 调整为反序 DP。修改状态定义为 $f[i][j]$ 为考虑 $[i, n - 1]$ 中进行选择,凑出无重叠子数组数量为 $j$ 个时的最大值(最终答案为 $f[0][3]$)。转移过程分析同理,然后从下标 $idx = 0$ 开始进行回溯,优先采纳 $idx$ 小的方案即可;
仍然采取正序 DP 的做法,但对原数组进行翻转,从而将回溯「字典序最大」转换为「字典序最小」具体方案。
一些细节:为了避免对边界的处理,我们令动规数组 $f$ 和前缀和数组 $sum$ 的下标从 $1$ 开始。
Java 代码(反序 DP
做法):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class Solution {
public int[] maxSumOfThreeSubarrays(int[] nums, int k) {
int n = nums.length;
long[] sum = new long[n + 1];
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + nums[i - 1];
long[][] f = new long[n + 10][4];
for (int i = n - k + 1; i >= 1; i--) {
for (int j = 1; j < 4; j++) {
f[i][j] = Math.max(f[i + 1][j], f[i + k][j - 1] + sum[i + k - 1] - sum[i - 1]);
}
}
int[] ans = new int[3];
int i = 1, j = 3, idx = 0;
while (j > 0) {
if (f[i + 1][j] > f[i + k][j - 1] + sum[i + k - 1] - sum[i - 1]) {
i++;
} else {
ans[idx++] = i - 1;
i += k; j--;
}
}
return ans;
}
}
C++ 代码(反序 DP
做法):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25class Solution {
public:
vector<int> maxSumOfThreeSubarrays(vector<int>& nums, int k) {
int n = nums.size();
vector<long long> sumv(n + 1, 0);
for (int i = 1; i <= n; i++) sumv[i] = sumv[i - 1] + nums[i - 1];
vector<vector<long long>> f(n + 10, vector<long long>(4, 0));
for (int i = n - k + 1; i >= 1; i--) {
for (int j = 1; j < 4; j++) {
f[i][j] = max(f[i + 1][j], f[i + k][j - 1] + sumv[i + k - 1] - sumv[i - 1]);
}
}
vector<int> ans(3);
int i = 1, j = 3, idx = 0;
while (j > 0) {
if (f[i + 1][j] > f[i + k][j - 1] + sumv[i + k - 1] - sumv[i - 1]) {
i++;
} else {
ans[idx++] = i - 1;
i += k; j--;
}
}
return ans;
}
};
Python 代码(反序 DP
做法):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class Solution:
def maxSumOfThreeSubarrays(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
sumv = [0] * (n + 1)
for i in range(1, n + 1):
sumv[i] = sumv[i - 1] + nums[i - 1]
f = [[0] * 4 for _ in range(n + 10)]
for i in range(n - k + 1, 0, -1):
for j in range(1, 4):
f[i][j] = max(f[i + 1][j], f[i + k][j - 1] + sumv[i + k -1] - sumv[i - 1])
ans = [0] * 3
i, j, idx = 1, 3, 0
while j > 0:
if f[i + 1][j] > f[i + k][j - 1] + sumv[i + k -1] - sumv[i - 1]:
i += 1
else:
ans[idx] = i - 1
idx += 1
i += k
j -= 1
return ans
TypeScript 代码(反序 DP
做法):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22function maxSumOfThreeSubarrays(nums: number[], k: number): number[] {
const n = nums.length;
const sumv = new Array(n + 1).fill(0);
for (let i = 1; i <= n; i++) sumv[i] = sumv[i - 1] + nums[i - 1];
const f = new Array(n + 10).fill(0).map(() => new Array(4).fill(0));
for (let i = n - k + 1; i >= 1; i--) {
for (let j = 1; j < 4; j++) {
f[i][j] = Math.max(f[i + 1][j], f[i + k][j - 1] + sumv[i + k - 1] - sumv[i - 1]);
}
}
const ans = new Array(3).fill(0);
let i = 1, j = 3, idx = 0;
while (j > 0) {
if (f[i + 1][j] > f[i + k][j - 1] + sumv[i + k - 1] - sumv[i - 1]) {
i++;
} else {
ans[idx++] = i - 1;
i += k; j--;
}
}
return ans;
};
Java 代码(翻转数组做法):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34class Solution {
public int[] maxSumOfThreeSubarrays(int[] nums, int k) {
int n = nums.length;
reverse(nums);
long[] sum = new long[n + 1];
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + nums[i - 1];
long[][] f = new long[n + 10][4];
for (int i = k; i <= n; i++) {
for (int j = 1; j < 4; j++) {
f[i][j] = Math.max(f[i - 1][j], f[i - k][j - 1] + sum[i] - sum[i - k]);
}
}
int[] ans = new int[3];
int i = n, j = 3, idx = 0;
while (j > 0) {
if (f[i - 1][j] > f[i - k][j - 1] + sum[i] - sum[i - k]) {
i--;
} else {
ans[idx++] = n - i;
i -= k; j--;
}
}
return ans;
}
void reverse(int[] nums) {
int n = nums.length;
int l = 0, r = n - 1;
while (l < r) {
int c = nums[l];
nums[l++] = nums[r];
nums[r--] = c;
}
}
}
C++ 代码(翻转数组做法):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26class Solution {
public:
vector<int> maxSumOfThreeSubarrays(vector<int>& nums, int k) {
reverse(nums.begin(), nums.end());
int n = nums.size();
vector<long long> sumv(n + 1, 0);
for (int i = 1; i <= n; i++) sumv[i] = sumv[i - 1] + nums[i - 1];
vector<vector<long long>> f(n + 2, vector<long long>(4, 0));
for (int i = k; i <= n; i++) {
for (int j = 1; j < 4; j++) {
f[i][j] = max(f[i - 1][j], f[i - k][j - 1] + sumv[i] - sumv[i - k]);
}
}
vector<int> ans(3);
int i = n, j = 3, idx = 0;
while (j > 0) {
if (f[i - 1][j] > f[i - k][j - 1] + sumv[i] - sumv[i - k]) {
i--;
} else {
ans[idx++] = n - i;
i -= k; j--;
}
}
return ans;
}
};
Python 代码(翻转数组做法):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class Solution:
def maxSumOfThreeSubarrays(self, nums: List[int], k: int) -> List[int]:
nums.reverse()
n = len(nums)
sumv = [0] * (n + 1)
for i in range(1, n + 1):
sumv[i] = sumv[i-1] + nums[i-1]
f = [[0] * 4 for _ in range(n + 2)]
for i in range(k, n + 1):
for j in range(1, 4):
f[i][j] = max(f[i - 1][j], f[i - k][j - 1] + sumv[i] - sumv[i - k]);
ans = [0] * 3
i, j, idx = n, 3, 0
while j > 0:
if f[i - 1][j] > f[i - k][j - 1] + sumv[i] - sumv[i - k]:
i -= 1
else:
ans[idx] = n - i
idx += 1
i -= k
j -= 1
return ans
TypeScript 代码(翻转数组做法):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24function maxSumOfThreeSubarrays(nums: number[], k: number): number[] {
nums.reverse();
const n = nums.length;
const sumv = new Array(n + 1).fill(0);
for (let i = 1; i <= n; i++) sumv[i] = sumv[i - 1] + nums[i - 1];
const f = Array.from({length: n + 2}, () => new Array(4).fill(0));
for (let i = k; i <= n; i++) {
for (let j = 1; j < 4; j++) {
f[i][j] = Math.max(f[i - 1][j], f[i - k][j - 1] + sumv[i] - sumv[i - k]);
}
}
const ans = new Array(3).fill(0);
let i = n, j = 3, idx = 0;
while (j > 0) {
if (f[i - 1][j] > f[i - k][j - 1] + sumv[i] - sumv[i - k]) {
i--;
} else {
ans[idx++] = n - i;
i -= k;
j--;
}
}
return ans;
};
- 时间复杂度:$O(n)$
- 空间复杂度:$O(n)$
最后
这是我们「刷穿 LeetCode」系列文章的第 No.689
篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!