There is a stone game.At the beginning of the game the player picks n piles of stones in a line.
The goal is to merge the stones in one pile observing the following rules:
At each step of the game,the player can merge two adjacent piles to a new pile.
The score is the number of stones in the new pile.
You are to determine the minimum of the total score.
Example
Example 1:
Input: [3, 4, 3]
Output: 17
Example 2:
Input: [4, 1, 1, 4]
Output: 18
Explanation:
先看一种错误的DP解法:错误在于
maxSum = max(maxSum, dp[i][k] + dp[k + 1][j]);
这个是不够的,因为还没有加上i和j之间的sum。光靠dp[i][i]和dp[i][i+1]的预处理不足以得到该sum。
class Solution {
public:
/**
* @param A: An integer array
* @return: An integer
*/
int stoneGame(vector &A) {
int n = A.size();
if (n == 0) return 0;
vector> dp(n, vector(n, 0));
for (int i = 0; i < n - 1; ++i) {
dp[i][i] = A[i];
dp[i][i + 1] = A[i] + A[i + 1];
}
dp[n - 1][n - 1] = A[n - 1];
for (int i = 0; i < n - 1; ++i) {
for (int j = 0; j < n; ++j) {
int maxSum = 0;
for (int k = i + 1; k < j; ++k) {
maxSum = max(maxSum, dp[i][k] + dp[k + 1][j]);
}
dp[i][j] = maxSum;
}
}
return dp[0][n - 1];
}
};
解法1:区间型DP。这道题的一个难点就是合并的过程中,A数组会变,所以DP过程中不能再用到A[i]。我们事先先把sums数组算好,这样A[i]变也没关系了。另外, dp[i][j]和sum[i]的坐标都是针对最初的数组。
这道题是一道区间dp的入门题,通过理解状态转移的过程来决定循环的要素。我们需要先枚举区间长度,再枚举起点。这就是区间dp的精髓。
dp[i][j]表示merge从i到j的stones的分数。
以输入[3,4,3]为例,最终dp[][]结果是:
0 7 17
0 0 7
0 0 0
我们可以看出,实际上这个DP算法只算了上半部的dp[][]值。dp[i][i]都没算。但是 这已经够了,我们只需要得到dp[0][n - 1]。
dp[][]计算顺序
dp[0][1], dp[1][2], dp[2][3], … , dp[n-2][n-1] //主对角线上面的那条对角线
dp[0][2], dp[1][3], dp[2][4], … , dp[n-3][n-1] //再上面一条对角线
…
dp[0][n-1] //右上方顶点
代码如下:
class Solution {
public:
/**
* @param A: An integer array
* @return: An integer
*/
int stoneGame(vector &A) {
int n = A.size();
if (n == 0) return 0;
vector> dp(n, vector(n, 0));
vector sums(n, 0);
sums[0] = A[0];
for (int i = 1; i < n; ++i) {
sums[i] = sums[i - 1] + A[i];
}
for (int len = 1; len <= n; ++len) {
for (int i = 0; i + len < n; ++i) {
dp[i][i + len] = INT_MAX;
int sum = sums[i + len] - sums[i - 1];
for (int k = i; k < i + len; ++k) {
dp[i][i + len] = min(dp[i][i + len], dp[i][k] + dp[k + 1][i + len] + sum);
}
}
}
return dp[0][n - 1];
}
};
解法2: DP + 递归
class Solution {
public:
/**
* @param A: An integer array
* @return: An integer
*/
int stoneGame(vector &A) {
int n = A.size();
if (n == 0) return 0;
dp.resize(n, vector(n, 0));
visited.resize(n, vector(n, false));
presums.resize(n, vector(n, 0));
for (int i = 0; i < n - 1; ++i) {
presums[i][i] = A[i];
for (int j = i + 1; j < n; ++j) {
presums[i][j] = presums[i][j - 1] + A[j];
}
}
return search(0, n - 1);
}
private:
int search(int start, int end) {
if (visited[start][end]) return dp[start][end];
if (start == end) {
visited[start][end] = true;
return dp[start][end];
}
dp[start][end] = INT_MAX;
for (int i = start; i < end; ++i) {
dp[start][end] = min(dp[start][end], search(start, i) + search(i + 1, end) + presums[start][end]);
}
visited[start][end] = true;
return dp[start][end];
}
vector> presums;
vector> visited;
vector> dp;
};