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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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;
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];
}
int[][][] dp = new int[n][n][K + 1];
return process2(0, n - 1, 1, stones, K, presum, dp);
}
// 因为上游调用的时候,一定确保都是有效的调用
// 核心是这一句 : for (int mid = L; mid < R; mid += K - 1) { ... }
// 这一句保证了一定都是有效调用
// 所以不需要写很多边界条件的判断,因为任何调用,都一定会返回正常答案
// mid每次跳K-1个位置这保证了左侧递归、右侧递归都是有效的
// 根本不会返回无效解的
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) {
return 0;
}
int ans = Integer.MAX_VALUE;
if (P == 1) {
ans = process2(L, R, K, arr, K, presum, dp) + presum[R + 1] - presum[L];
} 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);
ans = Math.min(ans, next1 + next2);
}
}
dp[L][R][P] = ans;
return ans;
}
// 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);
}
}
}
}