- Stone Game
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:
- Merge second and third piles => [4, 2, 4], score = 2
- Merge the first two piles => [6, 4],score = 8
- Merge the last two piles => [10], score = 18
先看一种错误的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<int> &A) {
int n = A.size();
if (n == 0) return 0;
vector<vector<int>> dp(n, vector<int>(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<int> &A) {
int n = A.size();
if (n == 0) return 0;
vector<vector<int>> dp(n, vector<int>(n, 0));
vector<int> 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];
}
};
二刷:
class Solution {
public:
/**
* @param a: An integer array
* @return: An integer
*/
int stoneGame(vector<int> &a) {
int n = a.size();
if (n == 0) return 0;
vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0));
a.insert(a.begin(), 0);
vector<int> sums(n + 1, 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 = 1; i + len <= n; i++) {
int j = i + len;
int sum = sums[j] - sums[i - 1];
dp[i][j] = INT_MAX;
for (int k = i; k < j; k++) {
dp [i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + sum);
}
}
}
return dp[1][n];
}
};
上面把dp[][]初始化为0, 但在循环内又将其初始化为INT_MAX。不好想。其实关键是dp[i][i]的初始化,可以将dp[i][i]初始化为0,其他元素初始化为无穷大即可。因为一个元素的合并值为0。
class Solution {
public:
/**
* @param a: An integer array
* @return: An integer
*/
int stoneGame(vector<int> &a) {
int n = a.size();
if (n == 0) return 0;
vector<vector<int>> dp(n + 1, vector<int>(n + 1, INT_MAX));
a.insert(a.begin(), 0);
//a.push_back(0);
vector<int> sums(n + 1, 0);
for (int i = 1; i <= n; i++) {
sums[i] = sums[i - 1] + a[i];
}
for (int i = 0; i <= n; i++) dp[i][i] = 0;
for (int len = 1; len <= n; len++) {
for (int i = 1; i + len <= n; i++) {
int j = i + len;
int sum = sums[j] - sums[i - 1];
//dp[i][j] = INT_MAX;
for (int k = i; k < j; k++) {
dp [i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + sum);
}
}
}
return dp[1][n];
}
};
解法2: DP + 递归
class Solution {
public:
/**
* @param A: An integer array
* @return: An integer
*/
int stoneGame(vector<int> &A) {
int n = A.size();
if (n == 0) return 0;
dp.resize(n, vector<int>(n, 0));
visited.resize(n, vector<bool>(n, false));
presums.resize(n, vector<int>(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<vector<int>> presums;
vector<vector<bool>> visited;
vector<vector<int>> dp;
};
二刷: 不需要visited数组,实际上sols[][]不为INT_MAX就说明已经处理过了。
class Solution {
public:
/**
* @param a: An integer array
* @return: An integer
*/
int stoneGame(vector<int> &a) {
int n = a.size();
if (n == 0) return 0;
sums.resize(n + 1, 0);
a.insert(a.begin(), 0);
vector<vector<int>> sols(n + 1, vector<int>(n + 1, INT_MAX));
for (int i = 1; i <= n; i++) {
sums[i] = sums[i - 1] + a[i];
}
return helper(a, 1, n, sols);
}
private:
vector<int> sums;
int helper(vector<int> &a, int start, int end, vector<vector<int>> &sols) {
int ret = INT_MAX;
if (start >= end) {
return 0;
}
if (sols[start][end] != INT_MAX) return sols[start][end];
int sum = sums[end] - sums[start - 1];
for (int i = start; i <= end; i++) {
sols[start][end] = min(sols[start][end],
helper(a, start, i, sols) + helper(a, i + 1, end, sols) + sum);
}
return sols[start][end];
}
};