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<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];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值