題目連接java
首先,K和石子數組的長度有關係,經過觀察可知,假設石子的長度是n,若是git
(n - 1) % (K - 1) > 0
則沒法最後合併成一個石子程序員
定義遞歸函數github
f(L, R, part, K)
L..R範圍上經過每次合併K個數,必定要合成出part個部分,最小代價是多少面試
因此主函數調用的時候算法
f(0, n - 1, 1, K)
0到n-1範圍上,經過每次合併K個數,必定要合成一部分,最小代價是多少數組
base case是, L...R範圍內只剩下一個數了,那麼若是part爲1,則返回最小代價是0(無須合併),若是part非1,則沒法合併(由於只有一個數了),返回-1微信
if (L == R) { return part == 1 ? 0 : -1; }
若是L...R範圍內不止一個數,那麼就看part的取值,數據結構
假如part等於1, 說明須要合併成一個數,那麼最後一次合併必然是分紅了K部分(由於分紅K部分,才能在下一次的合併過程當中合成一個數)函數
int pre = f(arr, L, R, K, K); if (pre == -1) { return -1; } return pre + L..R 累加和;
其中的L到R的累加和, 咱們先放一邊,再看下面一種狀況:
假如part的值不等於1,則須要考慮將L...R分兩部分來考慮,一部分生成part = 1的最小代價cost1,另一部分生成part = part - 1的最小代價cost2
則cost1 + cost2 就是最小代價,代碼以下:
int ans = Integer.MAX_VALUE; for (int i = L; i < R; i += (K - 1)) { int cost1 = f(arr, L, i, K, 1); int cost2 = f(arr, i + 1, R, K, part - 1); if (cost1 != -1 && cost2 != -1) { ans = Math.min(ans, cost2 + cost1); } } return ans;
接下來,咱們解決前面提到的問題:快速獲得L...R的累加和,咱們能夠經過前綴和數組來加速L..R的累加和計算, 生成前綴和數組的方式以下
// 前綴和用來加速求L..R範圍內的累加和 int[] preSum = new int[n]; preSum[0] = stones[0]; for (int i = 1; i < n; i++) { preSum[i] = preSum[i - 1] + stones[i]; }
這樣咱們就能夠很方便以O(1)求出L...R的累加和
L..R累加和 = preSum[R] - (L == 0?0:preSum[L-1])
因此,暴力遞歸的代碼以下:
// 暴力解法 public static int mergeStones(int[] stones, int K) { // k和數組長度先作一次過濾 int n = stones.length; if ((n - 1) % (K - 1) > 0) { return -1; } // 前綴和用來加速求L..R範圍內的累加和 int[] preSum = new int[n]; preSum[0] = stones[0]; for (int i = 1; i < n; i++) { preSum[i] = preSum[i - 1] + stones[i]; } return f(stones, 0, n - 1, K, 1, preSum); } // f(L,R,part) -> L..R範圍上必定要合成出part個數,最小代價是多少 public static int f(int[] arr, int L, int R, int K, int part, int[] preSum) { if (L == R) { return part == 1 ? 0 : -1; } if (part == 1) { // part只有1塊的時候 // 須要算出當part是K份的時候,最小代價 int pre = f(arr, L, R, K, K, preSum); if (pre == -1) { return -1; } return pre + preSum[R] - (L == 0 ? 0 : preSum[L - 1]); } // part不止一塊 // 則可讓 L..i 獲得1塊 // i+1...R獲得part-1塊 // 而後合併便可 int ans = Integer.MAX_VALUE; for (int i = L; i < R; i += (K - 1)) { int cost1 = f(arr, L, i, K, 1, preSum); int cost2 = f(arr, i + 1, R, K, part - 1, preSum); if (cost1 != -1 && cost2 != -1) { ans = Math.min(ans, cost2 + cost1); } } return ans; }
這個解法在LeetCode上超時,咱們能夠增長記憶化搜索來優化,如暴力嘗試中提到的,有三個可變參數:L,R,part, 其中:
L的變化範圍是:0...n-1
R的變化範圍是:0...n-1
part的變化範圍是:1...K
咱們能夠定義一個三維數組來存遞歸結果
int[][][] dp = new int[n][n][K+1]
只須要在每次暴力遞歸的時候,用這個數組存下當時的記錄便可, 優化後的代碼以下:
public static int mergeStones(int[] stones, int K) { // k和數組長度先作一次過濾 int n = stones.length; if ((n - 1) % (K - 1) > 0) { return -1; } // 前綴和用來加速求L..R範圍內的累加和 int[] preSum = new int[n]; preSum[0] = stones[0]; for (int i = 1; i < n; i++) { preSum[i] = preSum[i - 1] + stones[i]; } int[][][] dp = new int[n][n][K + 1]; return f2(stones, 0, n - 1, K, 1, preSum, dp); } // f(L,R,part) -> L..R範圍上必定要合成出part個數,最小代價是多少 public static int f2(int[] arr, int L, int R, int K, int part, int[] preSum, int[][][] dp) { if (dp[L][R][part] != 0) { return dp[L][R][part]; } if (L == R) { dp[L][R][part] = (part == 1 ? 0 : -1); return dp[L][R][part]; } if (part == 1) { // part只有1塊的時候 // 須要算出當part是K份的時候,最小代價 int pre = f2(arr, L, R, K, K, preSum, dp); if (pre == -1) { dp[L][R][part] = -1; return -1; } dp[L][R][part] = pre + preSum[R] - (L == 0 ? 0 : preSum[L - 1]); return dp[L][R][part]; } // part不止一塊 // 則可讓 L..i 獲得1塊 // i+1...R獲得part-1塊 // 而後合併便可 int ans = Integer.MAX_VALUE; for (int i = L; i < R; i += (K - 1)) { int left = f2(arr, L, i, K, 1, preSum, dp); int right = f2(arr, i + 1, R, K, part - 1, preSum, dp); if (left != -1 && right != -1) { ans = Math.min(ans, right + left); } else { dp[L][R][part] = -1; } } dp[L][R][part] = ans; return ans; }