题目:https://leetcode-cn.com/problems/minimum-cost-to-merge-stones/
区间dp
首先有解的条件为(n-1)%(K-1)==0
dp[i][j][k]表示把 第i堆石头到第j堆石头合并为k堆的最小代价
我们可以枚举中间点m,把左边分为一堆,把右边分为k-1的堆
那么我们有
dp[i][j][k] = min(dp[i][j][k], dp[i][m][1] + dp[m+1][j][k-1])
为什么不能是左边2堆,右边k-2堆呢?考虑左边合并为两堆,那么我们可以找一个点,划到右边,就还是左边一堆,右边k-1堆啦,所以其他所有的情况实际上都已经被 左边1堆,右边k-1堆覆盖了。
还有一个方程就是dp[i][j][1] = dp[i][j][K] + sum[j] - sum[i-1],注意是大写的K,就是说如果有K堆了,那么我们可以把它们合并为一堆,代价为这些石头的总和。
public class Leetcode1000 {
public static void main(String[] args) {
int i = new Leetcode1000().mergeStones(new int[]{3, 5, 1, 2, 6}, 3);
System.out.println(i);
}
static int INF = 0x3f3f3f3f;
public int mergeStones(int[] stones, int K) {
int n = stones.length;
int[] pre = new int[n + 1];
int[] a = new int[n + 1];
//下标改一下,方便操作
for (int i = 0; i < n; i++) {
a[i + 1] = stones[i];
pre[i + 1] = pre[i] + a[i + 1];
}
int[][][] dp = new int[n + 1][n + 1][n + 1];
if ((n - 1) % (K - 1) != 0) {
return -1;
}
//dp初始状态
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
for (int k = 1; k <= n; k++) {
dp[i][j][k] = INF;
}
}
}
for (int i = 1; i <= n; i++) {
dp[i][i][1] = 0;
}
//区间dp注意最外层枚举len
for (int len = 2; len <= n; len++) {
//枚举左端点得到右端点
for (int l = 1; l + len - 1 <= n; l++) {
int r = l + len - 1;
//枚举中点进行状态转移
for (int m = l; m < r; m++) {
//枚举合并成k堆
for (int k = 2; k <= len; k++) {
dp[l][r][k] = Math.min(dp[l][r][k], dp[l][m][k - 1] + dp[m + 1][r][1]);
System.out.printf("dp[%s][%s][%s]=%s", l, r, k, dp[l][r][k]);
System.out.println();
}
}
// System.out.printf("dp[%s][%s][%s]=%s", l, r, K, dp[l][r][K]);
System.out.println();
dp[l][r][1] = dp[l][r][K] + pre[r] - pre[l - 1];
// System.out.printf("dp[%s][%s][%s]=%s", l, r, 1, dp[l][r][1]);
System.out.println();
}
}
return dp[1][n][1];
}
}
优化:
1、通过编程可以发现,实际上很多时候dp[i][j][k]是没有值的,比如K=3的时候,dp[1][4][2],把4堆石头每次合并三个,是不可能合并成两堆的。通过这个是可以去优化我们的中间点m的,直接跳过不可能的点。
2、实际上i到j一直合并下去,能合并到的堆数是确定的,可以定义dp[i][j]为合并i到j的最小代价,这样就可以去掉第三维k,这个也是我在视频里看到的优化。