[Lintcode] triangle && 动态规划总结

Triangle

Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.

Example

For example, given the following triangle

[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]

 

The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).

Note

Bonus point if you are able to do this using only O(n) extra space, where n is the total number of rows in the triangle.

SOLUTION 1:

这个题特别适合总结DP方法,所以总结就在这题了。首先我们要知道为什么要动态规划,为啥不直接递归?很简单,递归时间复杂度太高了。为什么时间复杂度高?因为重复计算,实际上三角形里一共有(n^2)/2个点,实际算(n^2)/2次,O(n^2) 就够了,但是递归要计算多少次呢,递归要计算(2^n)/2,次,指数级别,太差了。怎么优化呢?cache一下就行了,现把所有计算结果放到hash里,然后重复计算时候就直接用,这样就只计算一次,就可以O(n^2)解决问题了。DP第一步就是这样,用记忆化搜索优化递归,达到DP效果。

记忆化搜索:这个方法我个人认为比DP好,不需要考虑转移方程,不需要考虑复杂的初始化,直接硬写,时间复杂度一样,多几行代码,少死几个脑细胞。

下面就是记忆化搜索模版:

核心就是先递归,做一个递归辅助函数,或者用divide & conquer也可以,写起来应该更简单。然后把所有结果存起来,如果flag标记一下,没标记过的存进去,标记过说明以前计算过,直接用,找到目标值就行了。

// 循环求所有状态
For I = 1 ->n
    dp[i] = search (i)
    // 搜索
    int search(int i) {
        if(visit[i] == 1){
            return dp[i];
            }
        if(smallest state){
            set smallest state
        } else {
        // to update (i,) , we might need other state
        // such as (i-1), (i+1)
        for other state
         update dp[i] = max(search(i-1) , search(i+1))
     }
    visit[i] = 1;
    return dp[i];
}                

 

看代码:

代码里用了几个全局变量,但是其实都是可以为了线程安全,放到search函数里。

public class Solution {
    /**
     * @param triangle: a list of lists of integers.
     * @return: An integer, minimum path sum.
     */
    private int n;
    private int[][] minSum;
    private int[][] triangle;
    private int search(int x, int y){
        if (x >= n){
            return 0;
        }
        if (minSum[x][y] != Integer.MAX_VALUE){
            return minSum[x][y];
        }
        minSum[x][y] = triangle[x][y] + Math.min(search(x + 1, y), search(x + 1, y + 1));
        return minSum[x][y];
    }
    public int minimumTotal(int[][] triangle) {
        if (triangle == null || triangle.length == 0){
            return -1;
        }
        if (triangle[0] == null || triangle[0].length == 0){
            return -1;
        }
        this.n = triangle.length;
        this.triangle = triangle;
        this.minSum = new int[n][n];
        for (int i = 0; i < n; i++){
            for (int j = 0; j < n; j++){
                minSum[i][j] = Integer.MAX_VALUE;
            }
        }
        return search(0, 0);
    }
}
View Code

 

SOLUTION 2:

再来说正经的动态规划,先来说自底向上的DP。

先复习下动归四大要素:

1,状态,就是比如说f(x)或者f(x,y)代表了什么含义。在这题里面,首先是二维的,所以是f(x,y), 它代表从底到(x,y)坐标点的最小距离。

2,状态转移方程,就是说由哪个位置的状态可以得到现在位置的状态。f(x,y) = Math.min( f(x + 1, y), f(x + 1, y + 1) ).

3,初始化。初始化就是说,最开始定义的几个状态,而这几个状态是用上面方程求不出来的。本题要初始化的三角形最后一行的所有值。

4,结果。就是返回值,最后返回的是什么,有的是返回最后一项,有的是返回结果数组里面最大或最小的,要看实际定义。本题要找的就是f(0 ,0)也就是顶端的值。

public class Solution {
    /**
     * @param triangle: a list of lists of integers.
     * @return: An integer, minimum path sum.
     */
    public int minimumTotal(int[][] triangle) {
        if (triangle == null || triangle.length == 0) {
            return -1;
        }
        if (triangle[0] == null || triangle[0].length == 0) {
            return -1;
        }
        int n = triangle.length;
        int[][] result = new int[n][n];
        // from result[m - 1][0....n - 1] to result[0][0]
        //initial
        for (int i = 0; i < n; i++){
            result[n - 1][i] = triangle[n - 1][i];
        }
        //function
        for (int i = n - 2; i >= 0; i--){
            for (int j = 0; j <= i; j++){
                result[i][j] = triangle[i][j] + Math.min(result[i + 1][j], result[i + 1][j + 1]);
            }
        }
        //ANS
        return result[0][0];
    }
}
View Code

 

SOLUTION 3:

自顶向下的动态规划。

自顶向下要比较麻烦一点,转移方程一样,但是初始化要初始化对角线所有点以及左侧边界所有点,结果要在最后一行里找最大值。

代码如下:

// version 0: top-down
public class Solution {
    /**
     * @param triangle: a list of lists of integers.
     * @return: An integer, minimum path sum.
     */
    public int minimumTotal(int[][] triangle) {
        if (triangle == null || triangle.length == 0) {
            return -1;
        }
        if (triangle[0] == null || triangle[0].length == 0) {
            return -1;
        }
        
        // state: f[x][y] = minimum path value from 0,0 to x,y
        int n = triangle.length;
        int[][] f = new int[n][n];
        
        // initialize 
        f[0][0] = triangle[0][0];
        for (int i = 1; i < n; i++) {
            f[i][0] = f[i - 1][0] + triangle[i][0];
            f[i][i] = f[i - 1][i - 1] + triangle[i][i];
        }
        
        // top down
        for (int i = 1; i < n; i++) {
            for (int j = 1; j < i; j++) {
                f[i][j] = Math.min(f[i - 1][j], f[i - 1][j - 1]) + triangle[i][j];
            }
        }
        
        // answer
        int best = f[n - 1][0];
        for (int i = 1; i < n; i++) {
            best = Math.min(best, f[n - 1][i]);
        }
        return best;
    }
}
View Code

 

总结:

首先,在一个题里,看到问题是

**最大值/最小值**最大路径/最小路径**结果总数**true/false**=====> 基本可以先去试试动态规划思路。

其次,在动态规划过程中,可以优化的就是空间,用滚动数组优化二维DP,循环指针优化一维DP。(其实就是取个 %)。

再然后就是动态规划中,如果转移方程不好计算,并且初始化不好找,果断使用记忆化搜索,开一个额外空间做flag,标记这个位置是否访问过,再纪录所有计算过程,可以做到跟DP一样的时间复杂度。(我认为这个方法可以说是最好用的吧,不需要太多思考,但是一定要画一个搜索树,画图,确定方程)。

最后注意,比如说word ladder这种题,单词中字母位置跟DP数组中对应位置是否一致,需要看自己定义状态跟初始化。

posted @ 2015-11-09 12:53  Tri_tri_tri  阅读(294)  评论(0)    收藏  举报