LeetCode 1043中等DP
分隔数组以得到最大和 图解题解
这道题到底在问什么
给定整数数组 arr 和上限 k。把 arr 分割成若干长度不超过 k 的连续子数组,每段所有元素替换为该段最大值。返回替换后整个数组元素和的最大值。
- 输入
- arr=[1,15,7,9,2,5,10], k=3
- 输出
- 84 (一种切法 [1,15,7] [9] [2,5,10]:15*3 + 9 + 10*3 = 84)
- 输入
- arr=[1], k=1
- 输出
- 1 (只有一个数,自成一段)
最优解:一步一步想明白
- 3一句话套路:dp[i] 只看「最后一段切多长」,前面交给 dp[i-len]。下面逐格填这张 dp 表,套的就是这一条规则。
- 4先看 dp 表。dp[0]=0 是地基:一个元素都不取,和当然是 0。后面每一格都会站在前面的格子上算出来。
- 5轮到 dp[1](紫色)。它的「最后一段」最多 1 个数,我们把 len 从 1 试到 1,每种切法都算一遍,取最大那个。
- 6试 len=1:最后一段是 [1],段内最大值 1,整段替换后贡献 1*1=1;再加上前面的最优 dp[0]=0(蓝色依赖格),候选总和 1。
- 7候选 1 比目前的 dp[1] 更大,更新 dp[1]=1。
- 8所有 len 试完,dp[1]=1 锁定(变绿)。它代表「前 1 个元素」切好后的最大和,后面的格子可以直接拿来用。
- 9轮到 dp[2](紫色)。它的「最后一段」最多 2 个数,我们把 len 从 1 试到 2,每种切法都算一遍,取最大那个。
- 10试 len=1:最后一段是 [15],段内最大值 15,整段替换后贡献 15*1=15;再加上前面的最优 dp[1]=1(蓝色依赖格),候选总和 16。
- 11候选 16 比目前的 dp[2] 更大,更新 dp[2]=16。
- 12试 len=2:最后一段是 [1,15],段内最大值 15,整段替换后贡献 15*2=30;再加上前面的最优 dp[0]=0(蓝色依赖格),候选总和 30。
- 13候选 30 比目前的 dp[2] 更大,更新 dp[2]=30。
- 14所有 len 试完,dp[2]=30 锁定(变绿)。它代表「前 2 个元素」切好后的最大和,后面的格子可以直接拿来用。
- 15轮到 dp[3](紫色)。它的「最后一段」最多 3 个数,我们把 len 从 1 试到 3,每种切法都算一遍,取最大那个。
- 16试 len=1:最后一段是 [7],段内最大值 7,整段替换后贡献 7*1=7;再加上前面的最优 dp[2]=30(蓝色依赖格),候选总和 37。
- 17候选 37 比目前的 dp[3] 更大,更新 dp[3]=37。
- 18试 len=2:最后一段是 [15,7],段内最大值 15,整段替换后贡献 15*2=30;再加上前面的最优 dp[1]=1(蓝色依赖格),候选总和 31。
- 19候选 31 没超过现在的 dp[3]=37,这种切法不划算,保留原值。
- 20试 len=3:最后一段是 [1,15,7],段内最大值 15,整段替换后贡献 15*3=45;再加上前面的最优 dp[0]=0(蓝色依赖格),候选总和 45。
- 21候选 45 比目前的 dp[3] 更大,更新 dp[3]=45。
- 22所有 len 试完,dp[3]=45 锁定(变绿)。它代表「前 3 个元素」切好后的最大和,后面的格子可以直接拿来用。
- 23轮到 dp[4](紫色)。它的「最后一段」最多 3 个数,我们把 len 从 1 试到 3,每种切法都算一遍,取最大那个。
- 24试 len=1:最后一段是 [9],段内最大值 9,整段替换后贡献 9*1=9;再加上前面的最优 dp[3]=45(蓝色依赖格),候选总和 54。
- 25候选 54 比目前的 dp[4] 更大,更新 dp[4]=54。
- 26试 len=2:最后一段是 [7,9],段内最大值 9,整段替换后贡献 9*2=18;再加上前面的最优 dp[2]=30(蓝色依赖格),候选总和 48。
- 27候选 48 没超过现在的 dp[4]=54,这种切法不划算,保留原值。
- 28试 len=3:最后一段是 [15,7,9],段内最大值 15,整段替换后贡献 15*3=45;再加上前面的最优 dp[1]=1(蓝色依赖格),候选总和 46。
- 29候选 46 没超过现在的 dp[4]=54,这种切法不划算,保留原值。
- 30所有 len 试完,dp[4]=54 锁定(变绿)。它代表「前 4 个元素」切好后的最大和,后面的格子可以直接拿来用。
- 31轮到 dp[5](紫色)。它的「最后一段」最多 3 个数,我们把 len 从 1 试到 3,每种切法都算一遍,取最大那个。
- 32试 len=1:最后一段是 [2],段内最大值 2,整段替换后贡献 2*1=2;再加上前面的最优 dp[4]=54(蓝色依赖格),候选总和 56。
- 33候选 56 比目前的 dp[5] 更大,更新 dp[5]=56。
- 34试 len=2:最后一段是 [9,2],段内最大值 9,整段替换后贡献 9*2=18;再加上前面的最优 dp[3]=45(蓝色依赖格),候选总和 63。
- 35候选 63 比目前的 dp[5] 更大,更新 dp[5]=63。
- 36试 len=3:最后一段是 [7,9,2],段内最大值 9,整段替换后贡献 9*3=27;再加上前面的最优 dp[2]=30(蓝色依赖格),候选总和 57。
- 37候选 57 没超过现在的 dp[5]=63,这种切法不划算,保留原值。
- 38所有 len 试完,dp[5]=63 锁定(变绿)。它代表「前 5 个元素」切好后的最大和,后面的格子可以直接拿来用。
- 39轮到 dp[6](紫色)。它的「最后一段」最多 3 个数,我们把 len 从 1 试到 3,每种切法都算一遍,取最大那个。
- 40试 len=1:最后一段是 [5],段内最大值 5,整段替换后贡献 5*1=5;再加上前面的最优 dp[5]=63(蓝色依赖格),候选总和 68。
- 41候选 68 比目前的 dp[6] 更大,更新 dp[6]=68。
- 42试 len=2:最后一段是 [2,5],段内最大值 5,整段替换后贡献 5*2=10;再加上前面的最优 dp[4]=54(蓝色依赖格),候选总和 64。
- 43候选 64 没超过现在的 dp[6]=68,这种切法不划算,保留原值。
- 44试 len=3:最后一段是 [9,2,5],段内最大值 9,整段替换后贡献 9*3=27;再加上前面的最优 dp[3]=45(蓝色依赖格),候选总和 72。
- 45候选 72 比目前的 dp[6] 更大,更新 dp[6]=72。
- 46所有 len 试完,dp[6]=72 锁定(变绿)。它代表「前 6 个元素」切好后的最大和,后面的格子可以直接拿来用。
- 47轮到 dp[7](紫色)。它的「最后一段」最多 3 个数,我们把 len 从 1 试到 3,每种切法都算一遍,取最大那个。
- 48试 len=1:最后一段是 [10],段内最大值 10,整段替换后贡献 10*1=10;再加上前面的最优 dp[6]=72(蓝色依赖格),候选总和 82。
- 49候选 82 比目前的 dp[7] 更大,更新 dp[7]=82。
- 50试 len=2:最后一段是 [5,10],段内最大值 10,整段替换后贡献 10*2=20;再加上前面的最优 dp[5]=63(蓝色依赖格),候选总和 83。
- 51候选 83 比目前的 dp[7] 更大,更新 dp[7]=83。
- 52试 len=3:最后一段是 [2,5,10],段内最大值 10,整段替换后贡献 10*3=30;再加上前面的最优 dp[4]=54(蓝色依赖格),候选总和 84。
- 53候选 84 比目前的 dp[7] 更大,更新 dp[7]=84。
- 54所有 len 试完,dp[7]=84 锁定(变绿)。它代表「前 7 个元素」切好后的最大和,后面的格子可以直接拿来用。
- 55填到最后一格 dp[7]=84,就是把整个数组切好后能得到的最大总和。对应的一种切法是 [1,15,7] [9] [2,5,10]:15*3 + 9 + 10*3 = 84。
⚠️ 容易写错的地方
✗ 错:段内最大值每次从头重算
✓ 对:内层 len 增大时顺手用 max 维护 mx
段每次只向左扩一位,O(1) 更新即可,重算会多一层循环
✗ 错:len 越界,没和 i 取 min
✓ 对:len 上限是 min(k, i)
最后一段不能超过已有的 i 个元素,否则会用到不存在的 dp 下标
✗ 错:贡献写成段内求和
✓ 对:是段内最大值 mx 乘段长 len
题目要求整段替换成最大值,不是保留原值求和
完整代码(Python / C++ / Java)
Python
from typing import List
class Solution:
def maxSumAfterPartitioning(self, arr: List[int], k: int) -> int:
n = len(arr)
dp = [0] * (n + 1)
for i in range(1, n + 1):
mx = 0
for length in range(1, min(k, i) + 1):
mx = max(mx, arr[i - length])
dp[i] = max(dp[i], dp[i - length] + mx * length)
return dp[n]C++
#include <algorithm>
#include <vector>
using namespace std;
class Solution {
public:
int maxSumAfterPartitioning(vector<int>& arr, int k) {
int n = arr.size();
vector<int> dp(n + 1);
for (int i = 1; i <= n; ++i) {
int mx = 0;
for (int len = 1; len <= min(k, i); ++len) {
mx = max(mx, arr[i-len]);
dp[i] = max(dp[i], dp[i-len] + mx * len);
}
}
return dp[n];
}
};Java
import java.util.*;
class Solution {
public int maxSumAfterPartitioning(int[] arr, int k) {
int n = arr.length;
int[] dp = new int[n + 1];
for (int i = 1; i <= n; i++) {
int max = 0;
for (int len = 1; len <= Math.min(k, i); len++) {
max = Math.max(max, arr[i - len]);
dp[i] = Math.max(dp[i], dp[i - len] + max * len);
}
}
return dp[n];
}
}复杂度
时间
O(n·k)
每个 dp[i] 枚举最多 k 种段长,共 n 个 i
空间
O(n)
一维 dp 数组,长度 n+1
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 分隔数组以得到最大和 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么按「最后一段」拆分,而不是「第一段」?+
两种都可以,结果一样。按最后一段拆,dp[i] 自然依赖更小的 dp[i-len],下标方向统一、从左往右填表最顺。这是分段类 DP 的通用套路:固定枚举末段,把前缀交给更小的子问题。
能不能优化到比 O(n·k) 更快?+
一般实现就是 O(n·k),由于 k 可能等于 n,最坏 O(n²)。本题难以做到更优,因为每个 dp[i] 确实需要考察 k 种末段长度;不过常数很小,n 到 500 完全够快。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 分隔数组以得到最大和 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。