好的,我们来看一个在“区间动态规划”领域中具有代表性,且在您提供的列表中 未被详细、明确地作为独立标题讲述过 的经典问题:
题目:合并相邻石子堆的最小成本问题(每次合并的得分为两堆石子数量之和的平方,求最小总得分)
问题描述
我们有一排 n 堆石子,排成一行。第 i 堆石子有 stones[i] 个石子。我们的目标是将所有石子堆合并成一堆。
合并的规则是:每次只能合并相邻的两堆石子。合并的成本(或称为得分)定义为被合并的两堆石子的石子数量之和的平方。
例如,合并两堆石子,数量分别为 a 和 b,则本次合并的成本为 (a + b)^2。
在整个过程中,你需要不断地合并相邻堆,直到只剩下一堆为止。不同的合并顺序会导致总成本不同。
请你设计一个算法,计算出将所有石子堆合并成一堆的最小总成本。
示例:
输入:stones = [1, 2, 3]
输出:29
解释:
方案一:先合并前两堆,成本 (1+2)^2 = 9,得到新堆 [3, 3];再合并这两堆,成本 (3+3)^2 = 36;总成本 9 + 36 = 45。
方案二:先合并后两堆,成本 (2+3)^2 = 25,得到新堆 [1, 5];再合并这两堆,成本 (1+5)^2 = 36;总成本 25 + 36 = 61。
方案三(最优):先合并第1和第3堆?不可以,因为它们不相邻。
先合并1和2得 [3, 3],成本9;再合并得36,总45。
先合并2和3得 [1, 5],成本25;再合并得36,总61。
等等...等一下,似乎没有比45更小的了?让我再算算。
等等,我上面的计算有误。关键在于合并后新堆的石子数是两堆之和,这个和会参与下一次合并。
让我们用区间DP来正确计算:
对于 [1, 2, 3]:
前缀和:prefix = [0, 1, 3, 6](prefix[i]表示前i堆的和,prefix[0]=0)。
定义 dp[i][j] 为合并第 i 堆到第 j 堆(闭区间)成一堆的最小成本。
dp[1][3] 是我们要求的。
-
分割点
k=1:先合并[1, 2]再与3合并。- 合并
[1,2]:成本dp[1][2] = (1+2)^2 = 9。合并后这堆的数量是sum(1,2) = 3。 - 将
3(新堆) 与3(原第三堆)合并:成本(3+3)^2 = 36。 - 总成本:
dp[1][2] + dp[3][3] + (sum(1,2) + stones[3])^2?不对,因为dp[i][j]已经包含了合并i到j的所有成本,所以不能直接加(3+3)^2。 - 正确的递推是:
dp[1][3]当k=1时 =dp[1][1] + dp[2][3] + (sum(1,3))^2。dp[1][1] = 0(一堆无需合并)。dp[2][3] = (2+3)^2 = 25。- 总区间和
sum(1,3) = 6,平方为36。 - 所以成本 =
0 + 25 + 36 = 61。
- 合并
-
分割点
k=2:先合并[2,3]再与1合并。dp[1][3]=dp[1][2] + dp[3][3] + (sum(1,3))^2。dp[1][2] = 9。dp[3][3] = 0。sum(1,3)=6,平方36。- 所以成本 =
9 + 0 + 36 = 45。
比较 61 和 45,最小值为 45。
所以对于示例 [1, 2, 3],最小总成本是 45,而不是29。29 这个数字可能是其他题目的答案(比如和等于乘积)。让我们验证题目:成本是和的平方。那么 [1,2,3] 的结果就是 45。如果我的问题描述是“求最小总得分”,那么示例输出应该对应地改为 45。为了讲解的严谨性,我们以 [1, 2, 3] 的最小总成本 45 为正确答案来继续讲解。
解题思路分析
这是一道典型的区间动态规划问题。
- 状态定义:我们关心的是一个连续区间
[i, j]内的石子合并问题。定义dp[i][j]表示将下标从i到j的这(j-i+1)堆石子合并成一堆的最小总成本。最终答案是dp[0][n-1]。 - 状态转移:如何得到
dp[i][j]?考虑最后一次合并操作。在将区间[i, j]最终合并成一堆之前,它一定是由两堆合并而来的。假设最后合并时,左边那大堆是由区间[i, k]合并成的,右边那大堆是由区间[k+1, j]合并成的(其中i <= k < j)。- 合并
[i, k]的最小成本是dp[i][k]。 - 合并
[k+1, j]的最小成本是dp[k+1][j]。 - 最后一步合并的成本是:
(sum(i, j))^2,其中sum(i, j)是区间[i, j]所有石子的总数(即最终合并的两大堆各自石子数之和)。 - 因此,对于这个特定的分割点
k,总成本为:dp[i][k] + dp[k+1][j] + (sum(i, j))^2。 - 为了得到
dp[i][j]的最小值,我们需要枚举所有可能的分割点k,并取上述计算结果的最小值。
- 合并
- 基础情况:当区间只有一堆石子时(即
i == j),不需要合并,成本为0。所以dp[i][i] = 0。 - 计算顺序:由于计算大区间
[i, j]时,依赖于其内部更小的区间[i, k]和[k+1, j],所以我们必须按照区间长度从小到大的顺序来计算。先计算所有长度为2的区间,然后是长度为3的区间,...,最后是长度为n的区间。 - 区间和快速计算:为了高效计算任意区间
[i, j]的和,我们可以预先计算一个前缀和数组prefix,其中prefix[0]=0,prefix[i]=stones[0]+stones[1]+...+stones[i-1]。这样sum(i, j) = prefix[j+1] - prefix[i]。
详细解题步骤
我们以 stones = [1, 2, 3] 为例,n = 3。
-
初始化:
- 创建
dp表,大小为n x n,所有元素初始化为一个很大的数(比如无穷大inf)。 - 设置基础情况:
dp[i][i] = 0对于所有i。因为一堆石子不需要合并。 - 计算前缀和
prefix:prefix[0] = 0prefix[1] = stones[0] = 1prefix[2] = prefix[1] + stones[1] = 1 + 2 = 3prefix[3] = prefix[2] + stones[2] = 3 + 3 = 6
- 创建
-
按区间长度
len从小到大计算:- 长度
len = 2(即区间包含2堆石子):- 区间
[0, 1](石子[1, 2]):- 只有一种合并方式,
k只能等于0。 sum = prefix[2] - prefix[0] = 3 - 0 = 3。dp[0][1] = dp[0][0] + dp[1][1] + sum^2 = 0 + 0 + 3^2 = 9。
- 只有一种合并方式,
- 区间
[1, 2](石子[2, 3]):sum = prefix[3] - prefix[1] = 6 - 1 = 5。dp[1][2] = dp[1][1] + dp[2][2] + 5^2 = 0 + 0 + 25 = 25。
- 区间
- 长度
len = 3(即区间包含3堆石子,我们的目标区间):- 区间
[0, 2](石子[1, 2, 3]):- 枚举分割点
k从0到1。 sum = prefix[3] - prefix[0] = 6 - 0 = 6。- 当
k = 0:dp[0][2]候选值 =dp[0][0] + dp[1][2] + 6^2 = 0 + 25 + 36 = 61。
- 当
k = 1:dp[0][2]候选值 =dp[0][1] + dp[2][2] + 6^2 = 9 + 0 + 36 = 45。
- 取最小值:
dp[0][2] = min(61, 45) = 45。
- 枚举分割点
- 区间
- 长度
-
得到答案:
dp[0][n-1]即dp[0][2] = 45。
复杂度分析
- 时间复杂度:O(n³)。我们需要枚举所有区间
O(n²),对于每个区间,需要枚举其内部所有可能的分割点O(n)。 - 空间复杂度:O(n²),用于存储
dp表。使用前缀和数组的空间为 O(n)。
核心要点与总结
这道题是区间DP的“教科书式”应用,清晰地展示了该类问题的思考框架:
- 定义以区间为状态:
dp[i][j]表示解决子问题[i, j]的最优解。 - 寻找合并/分割点:考虑区间最后一步是如何形成的(通常是通过一个分割点
k将区间分成两个子区间分别处理,再合并)。 - 状态转移方程:
dp[i][j] = min_{i<=k<j} { dp[i][k] + dp[k+1][j] + cost(i, j, k) }。其中cost函数是合并两个子区间结果时的代价,这里非常关键:在这个问题中,cost是(sum(i, j))^2。请注意,这个代价与分割点k无关,只与整个区间的总和有关。这是因为无论你怎么分割、按什么顺序合并,最终把整个区间[i, j]合成一堆时,你都需要将它的所有石子加总一次,而最后一步合并的成本正是这个总和的平方。这简化了转移方程。 - 确定计算顺序:从小区间到大区间,保证在计算大区间时,它所依赖的小区间已经计算完毕。
- 利用前缀和优化:快速计算区间和,使
cost函数的计算为 O(1)。
通过理解这个经典模型,你可以触类旁通,解决很多其他“合并相邻元素”的区间DP问题,区别往往在于 cost 函数的定义(例如,有的题目成本是两堆石子数的乘积,或者是其他函数)。