1043. 分隔数组以得到最大和
题目描述
给定一个整数数组 arr 和一个整数 k,将该数组分隔为长度最多为 k 的一些连续子数组。分隔完成后,每个子数组中的所有值都会变为该子数组中的最大值。
返回将数组分隔变换后能够得到的元素最大和。
- 示例
input : arr = [1, 15, 7, 9, 2, 5, 10] k = 3
output: 84
最佳分割方案为:[1, 15, 7] [9] [2, 5, 10] 数组变为:[15, 15, 15, 9, 10, 10, 10]
题目解析
对于数组 arr,分割之后的每一段子数组的长度不能超过 k。
arr = [1, 15, 7, 9, 2, 5, 10]
k = 3
分割结果:[1, 15, 7] [9] [2, 5, 10]
例如我们从后向前分割去掉 数组 [2,5,10] 之后,需要处理的数组 为 [1, 15, 7, 9]
处理逻辑和处理整个数组的逻辑相同
由上,每一个问题都是和原问题相似的子问题,所以,可以用递归来解决。
- 设计递归参数签名
- 从后向前分割,完成一次分割之后,变化的是数组的长度。所以递归参数为 最后一段子数组的开始下标
- 如何找最后一段子数组的开始下标,题目给定了子数组的长度不能超过k
- 所以起点为 n - k + 1
- 递归逻辑
- 枚举最后一段子数组的开始下标 j = n - k + 1 递归起点 i = n - 1
- 由于所有值都要变成这段子数组的最大值, 一共 i - j + 1 个元素
- 所以有
- 最后一段子数组长度不能超过 k 所以 j 的最小为 max(i-k+1, 0) (j 不能是负数)
- 最后一段子数组的元素和,加上 dfs(j - 1) 这个子问题的结果,再取最大值,即为 dfs(i)
- 公式为
- python
class Solution:
def max_sum_after_partitioning(self, arr: List[int], k: int) -> int:
@cache # 优化点: 缓存装饰器,避免重复计算 dfs 的结果
def dfs(i: int) -> int:
res = mx = 0
for j in range(i, max(i - k, -1), -1):
mx = max(mx, arr[j])
res = max(res, dfs(j - 1) + (i - j + 1) * mx)
return res
return dfs(len(arr) - 1)
- Java
private int[] arr
// 优化点:存储递归结果,避免重复计算.
private int[] memo;
private int k;
public int maxSumAfterPartitioning(int[] arr, int k) {
this.arr = arr;
this.k = k;
int n = arr.length;
memo = new int[n];
Arrays.fill(memo, -1);
return dfs(n - 1);
}
private int dfs(int i) {
if(i < 0) {
return 0;
}
if(memo[i] != -1) {
return memo[i];
}
int res = 0;
for(int j = i,max_num = 0;j > i-k && j >= 0;j--) {
max_num = Math.max(max_num, arr[j]);
res = Math.max(res, dfs(j - 1) + (i - j + 1) * max_num);
}
return memo[i] = res;
}
- 翻译成递推
public int maxSumAfterPartitioning(int[] arr, int k) {
int n = arr.length;
int[] dp = new int[n+1];
for(int i = 0;i < n;i++) {
for(int j = i,max_num = 0;j > i - k && j >= 0;j--) {
max_num = Math.max(max_num, arr[j]);
dp[i+1] = Math.max(dp[i+1], dp[i] + (i - j + 1) * max_num);
}
}
return dp[n];
}
参考