LeetCode 1000. Minimum Cost to Merge Stones

題目描述

題目連接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;
	}

微信截圖_20210222001100.png

更多

算法和數據結構筆記

參考資料

相關文章
相關標籤/搜索