LintCode 476. Stone Game (区间类DP经典题!重要!)

  1. 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:

  1. Merge second and third piles => [4, 2, 4], score = 2
  2. Merge the first two piles => [6, 4],score = 8
  3. 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 &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;
};

你可能感兴趣的:(LintCode 476. Stone Game (区间类DP经典题!重要!))