LeetCode 1000 Minimum Cost to Merge Stones
思路
本题是区间DP的代表,类似的题目有Burst Balloons。
首先检查当前数组是否能够合并为一个pile。可以采用反推的方法:题目要求最后所有的stones能合并为1个,并且每次选连续的K个piles进行合并,那么每次合并,应该减少(K-1)个piles,一直减到1。那么意味着,初始堆数应该是 1 + (K-1)* Z,Z大于等于0。
故首先判断输入数组的大小,如果输入数组的长度为1,那么直接返回0,因为此时不需要合并;如果输入数组的长度减一后,不能被(K-1)整除,那么无法合并至1个堆,返回-1。
那么剩下的情况是绝对能成功合并的。
用dp[i][j]表示合并从stones[i]到stones[j](包括stones[i]和stones[j],且并不要求一定能合并至1个堆)所需要的最小cost。比如,对于输入数组[3,1,2,4]和K=3,此时显然不能合并为1个堆,但是仍然做合并,直到不能合并为止。在这个例子中,只能合并一次,并且最后剩下两个堆,不难考虑,对于长度为Len的输入数组,尽可能多的合并,那么最后的堆数为(Len - 1) % (K - 1) + 1。再次强调,dp[i][j]只表示尽可能多的合并,并不一定要合并到只剩下一个堆。
在上面这个例子中,dp[0][3] = min(dp[0][0] + dp[1][3], dp[0][2] + dp[3][3])。因为,我们总希望将问题切割成小问题,也就是dp[i][j] = dp[i][m] + dp[m+1][j],并且,希望区间[i, m]是能够正常合并至1个堆的,根据前面的推理,当区间[i, m]的长度为1 + (K-1)* Z时,区间总能够合并为1个堆。所以初始状态为m = i,此时区间[i, m]内只有一个数;下一个状态为m = i + K - 1,此时区间[i, m]内有K个数;下一个状态为m = i + K - 1 + K - 1,此时区间内有2K - 1个数,一直到m小于j。当m小于j时,m+1是等于j的。在这个过程中,我们总希望左边的这一项dp[i][m]所代表的区间是能够最终合并为1个堆的,但是右边的一项dp[m+1][j]并不保证最终能合并为1个堆,但这不重要,因为dp[m+1][j]表示尽可能压缩区间[m+1, j]所花费的最小cost。对于区间[m+1, j]当中没有被压缩的部分,cost为0,因为没有压缩,就没有开销。这两项相加后,dp[i][j]表示压缩区间[i, j]至最少堆数的最小cost。比如,dp[0][0] + dp[1][3]表示将[3,1,2,4]压缩为[3, 7],故表示:将拥有4个堆的输入[3,1,2,4]压缩为拥有2个堆的输出[3, 7]的开销为7。类似的,dp[0][2] + dp[3][3]表示将[3,1,2,4]压缩为[6,4]的开销为6。因此将[3,1,2,4]压缩的最小开销为6,那么dp[0][3] = 6。
接下来判断区间[i, j]是否能压缩为1个堆,区间长度为j - i + 1,带入上面的结论,((j - i + 1) - 1) % (K - 1)等于0的话,表示能将区间压缩至1个堆,开销为stones[i] + ... + stones[j]。
代码
解法一 Bottom up
class Solution {
public:
int mergeStones(vector<int>& stones, int K) {
if (stones.size() == 1) return 0;
if ((stones.size() - 1) % (K-1)) return -1;
vector<vector<int>> dp(stones.size(), vector<int>(stones.size()));
vector<int> prefixSum(stones.size() + 1);
prefixSum[0] = 0;
for (int i = 1; i < prefixSum.size(); ++i) {
prefixSum[i] = prefixSum[i-1] + stones[i-1];
}
// 子问题的规模从2开始,i和j是区间的边界
for (int len = 2; len <= stones.size(); ++len) {
for (int i = 0; i + len <= stones.size(); ++i) {
int j = i + len - 1;
dp[i][j] = INT_MAX;
for (int m = i; m < j; m += K-1) {
dp[i][j] = min(dp[i][j], dp[i][m] + dp[m+1][j]);
}
if ((j - i) % (K-1) == 0)
dp[i][j] += prefixSum[j+1] - prefixSum[i];
}
}
return dp.front().back();
}
};
解法二 top down
class Solution {
public:
int mergeStones(vector<int>& stones, int K) {
if (stones.size() == 1) return 0;
if ((stones.size() - 1) % (K - 1)) return -1;
vector<int> prefixSum(stones.size() + 1);
prefixSum[0] = 0;
for (int i = 1; i < prefixSum.size(); ++i)
prefixSum[i] = prefixSum[i - 1] + stones[i-1];
vector<vector<int>> dp(stones.size(), vector<int>(stones.size(), INT_MAX));
function<int (int, int)> helper = [&stones, &prefixSum, &dp, &helper, K] (int i, int j) {
if (j - i + 1 < K) return 0;
if (dp[i][j] != INT_MAX) return dp[i][j];
for (int m = i; m < j; m += K - 1) {
dp[i][j] = min(dp[i][j], helper(i, m) + helper(m+1, j));
}
if ((j - i) % (K - 1) == 0)
dp[i][j] += prefixSum[j+1] - prefixSum[i];
return dp[i][j];
};
return helper(0, stones.size() - 1);
}
};