1. 问题描述:
给定数组 nums 由正整数组成,找到三个互不重叠的子数组的最大和。每个子数组的长度为k,我们要使这3 * k个项的和最大化。返回每个区间起始索引的列表(索引从 0 开始)。如果有多个结果,返回字典序最小的一个。
示例:
输入: [1,2,1,2,6,7,5,1], 2
输出: [0, 3, 5]
解释: 子数组 [1, 2], [2, 6], [7, 5] 对应的起始索引为 [0, 3, 5]。
我们也可以取 [2, 1], 但是结果 [1, 3, 5] 在字典序上更大。
注意:
nums.length的范围在[1, 20000]之间。
nums[i]的范围在[1, 65535]之间。
k的范围在[1, floor(nums.length / 3)]之间。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/maximum-sum-of-3-non-overlapping-subarrays
2. 思路分析:
分析题目可以知道我们需要求解出字典序最小并且满足题目要求的方案,所以我们需要尝试计算出所有满足题目要求的方案,在这些方案中求解出字典序最小的方案,因为nums的长度最大在20000所以我们考虑使用动态规划解决。动态规划主要有两个步骤:① 状态表示 ② 状态计算;根据题目的描述我们可以定义二维dp数组,其中dp[i][j]表示前i个数中找到j个不重叠子数组的最大和,怎么样进行状态计算呢?状态计算对应集合的划分,通常是找最后一个不同点,我们以第i个数作为不同点,可以将集合划分为两类,第一类是前i个数中的j段不重叠的子数组包含了位置i对应的数字,第二类是不包含位置i对应的数字,划分为两大类之后我们就可以进行状态的计算了。对于第一类有dp[i - k][j - 1] + si - sk,第二类有dp[i - 1][j],两类中取一个最大值即可。我们可以使用两层循环进行状态计算,第一层循环表示前i个数,第二层循环表示当前有j个不重叠的子数组。由于这道题目需要求解的是字典序最小的方案所以我们在递推的时候需要从后往前枚举前i个数,这样才可以求解出第一段字典序最小的位置,然后再从这个位置找到其余两段字典序最小不重叠子数组即可。
3. 代码如下:
from typing import List
class Solution:
def maxSumOfThreeSubarrays(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
s = [0] * (n + 1)
dp = [[0] * 4 for i in range(n + 2)]
# 前缀和
for i in range(1, n + 1):
s[i] = s[i - 1] + nums[i - 1]
x = n + 1
# 因为需要求解字典序最小的方案所以逆着递推这样最终才可以求解出一开始字典序最小的方案的下标
# 逆序递推最后一段最小的下标为n - k + 1这里的下标是从1开始的
for i in range(n - k + 1, 0, -1):
for j in range(1, 4):
dp[i][j] = max(dp[i + 1][j], dp[i + k][j - 1] + s[i + k - 1] - s[i - 1])
if dp[i][3] >= dp[x][3]: x = i
# 从前往后求解这样可以求出字典序最小的方案
y = 3
res = list()
while y > 0:
# 当循环结束之后那么说明找到了当前满足的一段那么字典序肯定是最小的, 第一次的时候因为dp[x][y]一定满足要求的所以一定不会进入循环, 只有当当前这一段不满足题目要求的时候才进入循环
while dp[x][y] != dp[x + k][y - 1] + s[x + k - 1] - s[x - 1]: x += 1
res.append(x - 1)
x += k
y -= 1
return res