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 * 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$ 和前缀和数组 $sun$ 的下标从 $1$ 开始。
代码($P1$ 为反序 DP 做法,$P2$ 为翻转数组做法):
1 |
|
1 |
|
- 时间复杂度:$O(n)$
- 空间复杂度:$O(n)$
最后
这是我们「刷穿 LeetCode」系列文章的第 No.689
篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!