合并石头的最低成本【LC1000】
有
N
堆石头排成一排,第i
堆中有stones[i]
块石头。每次*移动(move)*需要将连续的
K
堆石头合并为一堆,而这个移动的成本为这K
堆石头的总数。找出把所有石头合并成一堆的最低成本。如果不可能,返回
-1
。
等校车的时候用手机看了一眼,感觉是区间dp,但这几天都在练习区间dp,心里想不会那么巧吧
然后坐下来就没往区间dp想,首先先确定石头堆数和K的关系,然后想到贪心,先合并的K堆石, 在之后还会与其他石头进行合并,因此优先合并连续k堆石头最小的k堆,然后删除合并的石头,加入新合并后的,然后再进行删除,直到石头的堆数为k。然后遇到了错误的案例,上面的贪心不是最优的,然后想着那就每次枚举所有的 k k k堆石头吧,然后超时了。最后放弃,看题解,emm,真的是区间dp…
递归+记忆化
-
思路
-
确定石头堆数 n n n和 k k k的关系
我们最终需要将 n n n堆石头合并成1堆,需要减少 n − 1 n-1 n−1堆,每次操作可以合并 k k k堆石头,可以减少 k − 1 k-1 k−1堆石头。那么 n − 1 n-1 n−1必须可以整除 k − 1 k-1 k−1,才可以合并成1堆石头。因此当不能整除时,直接返回-1。
-
找到子问题
- 最后一步一定是从 n = k n=k n=k堆石头合并成1堆石头,或者本身就只有一堆石头,合并的代价为这些石头的总和
- 第一堆石头是怎么得到的?
- 就是初始情况下的stone[0]
- 通过一次合并得到
- 通过两次合并得到
- 对于右边剩余的石头,我们需要计算的是把这些石头堆合并成 k − 1 k-1 k−1堆需要的最低成本
因此可以定义递归函数 d f s ( i , j , p ) dfs(i,j,p) dfs(i,j,p)表示将第 i i i堆至第 j j j堆石头合并成 p p p堆的最低成本
-
递归过程:
-
如果p为1,那么下一个子问题时,将这些石头合并成 k k k堆的最小成本,即 d f s ( i , j , k ) dfs(i,j,k) dfs(i,j,k)
d f s ( i , j , 1 ) = d f s ( i , j , k ) + ∑ q = i j s t o n e [ q ] dfs(i,j,1)=dfs(i,j,k)+\sum _{q=i}^{j}stone[q] dfs(i,j,1)=dfs(i,j,k)+q=i∑jstone[q]
某个区间之和使用前缀和数组优化 -
如果p不为1,那么将石头分为两部分,以m为分割点,使左边部可以合并为1堆,因此 m = i + ( k − 1 ) x m=i+(k-1)x m=i+(k−1)x,那么剩余部分合并为 p − 1 p-1 p−1堆
d f s ( i , j , p ) = m i n m = i + ( k − 1 ) x d f s ( i , m , 1 ) + d f s ( m + 1 , j , p − 1 ) dfs(i,j,p)=min_{m=i+(k-1)x}{dfs(i,m,1)+dfs(m+1,j,p-1)} dfs(i,j,p)=minm=i+(k−1)xdfs(i,m,1)+dfs(m+1,j,p−1)
-
-
递归边界
d f s ( i , i , 1 ) = 0 dfs(i,i,1)=0 dfs(i,i,1)=0 ,只有一堆石头,不需要合并
-
递归入口
d f s ( 0 , n − 1 , 1 ) dfs(0,n-1,1) dfs(0,n−1,1)
优化:
-
-
实现
class Solution { int[][][] dp; int[] sum; int k; public int mergeStones(int[] stones, int k) { int n = stones.length; if ((n - 1) % (k - 1) != 0) return -1; dp = new int[n + 1][n + 1][k + 1]; sum = new int[n + 1]; this.k = k; for (int i = 0; i <= n; i++){ for (int j = 0; j <= n; j++){ Arrays.fill(dp[i][j], -1); } } for (int i = 0; i < n; i++){ sum[i + 1] = sum[i] + stones[i]; } return dfs(0, n - 1 , 1); } public int dfs(int i, int j, int p){ if (dp[i][j][p] != -1) return dp[i][j][p]; if (p == 1){ return i == j ? 0 : dfs(i, j, k) + sum[j + 1] - sum[i]; } int res = Integer.MAX_VALUE; for (int m = i; m < j; m += k - 1){ res = Math.min(res, dfs(i, m, 1) + dfs(m + 1, j, p - 1)); } dp[i][j][p] = res; return res; } }
-
复杂度
- 时间复杂度: O ( n 3 ) O(n^3) O(n3),其中 n 为 stones 的长度。动态规划的时间复杂度 = 状态个数 × 单个状态的计算时间。这里状态个数为 O ( n 2 k ) O(n^2k) O(n2k),单个状态的计算时间为 O ( n / k ) O(n/k) O(n/k) ,因此时间复杂度为 O ( n 3 ) O(n^3) O(n3)
- 空间复杂度: O ( n 2 ∗ k ) O(n^2*k) O(n2∗k)
-
-
实现:优化
class Solution { private int[][] memo; private int[] s; private int k; public int mergeStones(int[] stones, int k) { int n = stones.length; if ((n - 1) % (k - 1) > 0) // 无法合并成一堆 return -1; s = new int[n + 1]; for (int i = 0; i < n; i++) s[i + 1] = s[i] + stones[i]; // 前缀和 this.k = k; memo = new int[n][n]; for (int i = 0; i < n; ++i) Arrays.fill(memo[i], -1); // -1 表示还没有计算过 return dfs(0, n - 1); } private int dfs(int i, int j) { if (i == j) return 0; // 只有一堆石头,无需合并 if (memo[i][j] != -1) return memo[i][j]; int res = Integer.MAX_VALUE; for (int m = i; m < j; m += k - 1) res = Math.min(res, dfs(i, m) + dfs(m + 1, j)); if ((j - i) % (k - 1) == 0) // 可以合并成一堆 res += s[j + 1] - s[i]; return memo[i][j] = res; } } 作者:灵茶山艾府 链接:https://leetcode.cn/problems/minimum-cost-to-merge-stones/solutions/2207235/tu-jie-qu-jian-dpzhuang-tai-she-ji-yu-yo-ppv0/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
-
复杂度
- 时间复杂度: O ( n 3 / k ) O(n^3/k) O(n3/k),其中 n 为 stones 的长度。动态规划的时间复杂度 = 状态个数 × 单个状态的计算时间。这里状态个数为 O ( n 2 ) O(n^2) O(n2),单个状态的计算时间为 O ( n / k ) O(n/k) O(n/k) ,因此时间复杂度为 O ( n 3 / k ) O(n^3/k) O(n3/k)
- 空间复杂度: O ( n 2 ∗ k ) O(n^2*k) O(n2∗k)
-
动态规划
class Solution {
public int mergeStones(int[] stones, int k) {
int n = stones.length;
if ((n - 1) % (k - 1) > 0) // 无法合并成一堆
return -1;
var s = new int[n + 1];
for (int i = 0; i < n; i++)
s[i + 1] = s[i] + stones[i]; // 前缀和
var f = new int[n][n];
for (int i = n - 1; i >= 0; --i)
for (int j = i + 1; j < n; ++j) {
f[i][j] = Integer.MAX_VALUE;
for (int m = i; m < j; m += k - 1)
f[i][j] = Math.min(f[i][j], f[i][m] + f[m + 1][j]);
if ((j - i) % (k - 1) == 0) // 可以合并成一堆
f[i][j] += s[j + 1] - s[i];
}
return f[0][n - 1];
}
}
作者:灵茶山艾府
链接:https://leetcode.cn/problems/minimum-cost-to-merge-stones/solutions/2207235/tu-jie-qu-jian-dpzhuang-tai-she-ji-yu-yo-ppv0/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
-
复杂度
- 时间复杂度: O ( n 3 / k ) O(n^3/k) O(n3/k),其中 n 为 stones 的长度。动态规划的时间复杂度 = 状态个数 × 单个状态的计算时间。这里状态个数为 O ( n 2 ) O(n^2) O(n2),单个状态的计算时间为 O ( n / k ) O(n/k) O(n/k) ,因此时间复杂度为 O ( n 3 / k ) O(n^3/k) O(n3/k)
- 空间复杂度: O ( n 2 ∗ k ) O(n^2*k) O(n2∗k)