1043. 分隔数组以得到最大和
难度:中等
题目:
给你一个整数数组 arr
,请你将该数组分隔为长度 最多 为 k 的一些(连续)子数组。分隔完成后,每个子数组的中的所有值都会变为该子数组中的最大值。
返回将数组分隔变换后能够得到的元素最大和。本题所用到的测试用例会确保答案是一个 32 位整数。
示例 1:
输入: arr = [1,15,7,9,2,5,10], k = 3
输出: 84
解释: 数组变为 [15,15,15,9,10,10,10]
示例 2:
输入: arr = [1,4,1,5,7,3,6,1,9,9,3], k = 4
输出: 83
示例 3:
输入: arr = [1], k = 1
输出: 1
提示:
1 <= arr.length <= 500
0 <= arr[i] <= 109
1 <= k <= arr.length
个人思路
思路与算法
解法一 动态规划
对于 arr=[1,15,7,9,2,5,10],k=3,最后一段子数组可以是 [10],[5,10],[2,5,10](长度不能超过 k=3)。根据题意,将子数组内的元素变成子数组的最大值,例如 [2,5,10] 变成 [10,10,10],其元素和为 30。
去掉这段子数组,例如去掉 [2,5,10],剩余的要解决的问题是 [1,15,7,9] 在分隔变换后能够得到的元素最大和。由于这是一个和原问题相似的子问题,所以可以用递归解决。
根据上面的讨论,递归参数只需要一个 i。定义 dfs(i) 表示把 arr[0] 到 arr[i] 这段做分隔变换后能够得到的元素最大和。
枚举最后一段的子数组的开始下标 j,那么从 arr[j] 到 arr[i] 的这段子数组的所有值都要变为这段子数组的最大值,这一共有 i−j+1 个元素,对应的元素和就是
由于最后一段子数组的长度不能超过 k,所以 j 最小为 max(i−k+1,0)(注意 j 不能是负数)。
把最后一段子数组的元素和,加上 dfs(j−1) 这个子问题的结果,再取最大值,就是 dfs(i)。写成式子就是
递归边界:dfs(−1)=0。一个数都没有,元素和为 0。
递归入口:dfs(n−1),也就是答案。
代码实现时,可以从 i 开始倒着枚举 j,一边枚举一边计算子数组的最大值,从而 O(1) 地计算出元素和。
代码
class Solution {
public:
int maxSumAfterPartitioning(vector<int> &arr, int k) {
function<int(int)> dfs = [&](int i) -> int {
// i=-1 时不会进入循环
int res = 0;
for (int j = i, mx = 0; j > i - k && j >= 0; --j) {
mx = max(mx, arr[j]); // 一边枚举 j,一边计算子数组的最大值
res = max(res, dfs(j - 1) + (i - j + 1) * mx);
}
return res;
};
return dfs(arr.size() - 1);
}
};
解法二 递归 + 记录返回值 = 记忆化搜索
上面的做法太慢了,怎么优化呢?
举个例子,「先分隔出 arr[n−1],再分隔出 arr[n−2](两个子数组)」和「分隔出 arr[n−2] 到 arr[n−1](一个子数组)」,都会递归到 dfs(n−3)。
一叶知秋,整个递归中有大量重复递归调用(递归入参相同)。由于递归函数没有副作用,同样的入参无论计算多少次,算出来的结果都是一样的,因此可以用记忆化搜索来优化:
如果一个状态(递归入参)是第一次遇到,那么可以在返回前,把状态及其结果记到一个 memo 数组(或哈希表)中。 如果一个状态不是第一次遇到,那么直接返回 memo 中保存的结果。
class Solution {
public:
int maxSumAfterPartitioning(vector<int> &arr, int k) {
int n = arr.size(), memo[n];
memset(memo, -1, sizeof(memo)); // -1 表示还没有计算过
function<int(int)> dfs = [&](int i) -> int {
if (i < 0) return 0;
int &res = memo[i]; // 注意这里是引用,下面会直接修改 memo[i]
if (res != -1) return res; // 之前计算过了
for (int j = i, mx = 0; j > i - k && j >= 0; --j) {
mx = max(mx, arr[j]); // 一边枚举 j,一边计算子数组的最大值
res = max(res, dfs(j - 1) + (i - j + 1) * mx);
}
return res;
};
return dfs(n - 1);
}
};
每天记录一下做题思路。