You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

153 lines
4.2 KiB

2 years ago
package class23;
// 本题测试链接 : https://leetcode.com/problems/minimum-cost-to-merge-stones/
public class Code05_MinimumCostToMergeStones {
// // arr[L...R]一定要整出P份合并的最小代价返回
// public static int f(int[] arr, int K, int L, int R, int P) {
// if(从L到R根本不可能弄出P份) {
// return Integer.MAX_VALUE;
// }
// // 真的有可能的!
// if(P == 1) {
// return L == R ? 0 : (f(arr, K, L, R, K) + 最后一次大合并的代价);
// }
// int ans = Integer.MAX_VALUE;
// // 真的有可能P > 1
// for(int i = L; i < R;i++) {
// // L..i(1份) i+1...R(P-1份)
// int left = f(arr, K, L, i, 1);
// if(left == Integer.MAX_VALUE) {
// continue;
// }
// int right = f(arr, K, i+1,R,P-1);
// if(right == Integer.MAX_VALUE) {
// continue;
// }
// int all = left + right;
// ans = Math.min(ans, all);
// }
// return ans;
// }
public static int mergeStones1(int[] stones, int K) {
int n = stones.length;
if ((n - 1) % (K - 1) > 0) {
return -1;
}
int[] presum = new int[n + 1];
for (int i = 0; i < n; i++) {
presum[i + 1] = presum[i] + stones[i];
}
return process1(0, n - 1, 1, stones, K, presum);
}
// part >= 1
// arr[L..R] 一定要弄出part份返回最低代价
// arr、K、presum前缀累加和数组求i..j的累加和就是O(1)了)
public static int process1(int L, int R, int P, int[] arr, int K, int[] presum) {
if (L == R) { // arr[L..R]
return P == 1 ? 0 : -1;
}
// L ... R 不只一个数
if (P == 1) {
int next = process1(L, R, K, arr, K, presum);
if (next == -1) {
return -1;
} else {
return next + presum[R + 1] - presum[L];
}
} else { // P > 1
int ans = Integer.MAX_VALUE;
// L...mid是第1块剩下的是part-1块
for (int mid = L; mid < R; mid += K - 1) {
// L..mid(一份) mid+1...R(part - 1)
int next1 = process1(L, mid, 1, arr, K, presum);
int next2 = process1(mid + 1, R, P - 1, arr, K, presum);
if (next1 != -1 && next2 != -1) {
ans = Math.min(ans, next1 + next2);
}
}
return ans;
}
}
public static int mergeStones2(int[] stones, int K) {
int n = stones.length;
1 year ago
if ((n - 1) % (K - 1) > 0) {
2 years ago
return -1;
}
int[] presum = new int[n + 1];
for (int i = 0; i < n; i++) {
presum[i + 1] = presum[i] + stones[i];
}
int[][][] dp = new int[n][n][K + 1];
return process2(0, n - 1, 1, stones, K, presum, dp);
}
1 year ago
// 因为上游调用的时候,一定确保都是有效的调用
// 核心是这一句 : for (int mid = L; mid < R; mid += K - 1) { ... }
// 这一句保证了一定都是有效调用
// 所以不需要写很多边界条件的判断,因为任何调用,都一定会返回正常答案
// mid每次跳K-1个位置这保证了左侧递归、右侧递归都是有效的
// 根本不会返回无效解的
2 years ago
public static int process2(int L, int R, int P, int[] arr, int K, int[] presum, int[][][] dp) {
if (dp[L][R][P] != 0) {
return dp[L][R][P];
}
if (L == R) {
1 year ago
return 0;
2 years ago
}
1 year ago
int ans = Integer.MAX_VALUE;
2 years ago
if (P == 1) {
1 year ago
ans = process2(L, R, K, arr, K, presum, dp) + presum[R + 1] - presum[L];
2 years ago
} else {
for (int mid = L; mid < R; mid += K - 1) {
int next1 = process2(L, mid, 1, arr, K, presum, dp);
int next2 = process2(mid + 1, R, P - 1, arr, K, presum, dp);
1 year ago
ans = Math.min(ans, next1 + next2);
2 years ago
}
}
1 year ago
dp[L][R][P] = ans;
return ans;
2 years ago
}
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) (maxSize * Math.random()) + 1];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random());
}
return arr;
}
// for test
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int maxSize = 12;
int maxValue = 100;
System.out.println("Test begin");
for (int testTime = 0; testTime < 100000; testTime++) {
int[] arr = generateRandomArray(maxSize, maxValue);
int K = (int) (Math.random() * 7) + 2;
int ans1 = mergeStones1(arr, K);
int ans2 = mergeStones2(arr, K);
if (ans1 != ans2) {
System.out.println(ans1);
System.out.println(ans2);
}
}
}
}