动态规划问题

Posted futurehau

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划问题相关的知识,希望对你有一定的参考价值。

递归 VS 动态规划,这里通过数三角形问题来引入递归与动态规划的区别

递归 + 记忆化搜索 —> 动态规划。

分治法与递归实现的动态规划的区别:有没有重复计算。

 

 

数字三角形 

给定一个数字三角形,找到从顶部到底部的最小路径和。每一步可以移动到下面一行的相邻数字上。这里记录n为数字三角形的行数。

方法一:traverse。相当于找出所有的路径,而路径数一共有2^n 条。所以很自然,这种方法超时了。

技术分享
 1 private int best = Integer.MAX_VALUE;
 2     /**
 3      * @param triangle: a list of lists of integers.
 4      * @return: An integer, minimum path sum.
 5      */
 6     public int minimumTotal(int[][] triangle) {
 7         //traverse
 8         if (triangle == null || triangle.length == 0) {
 9             return -1;
10         }
11         traverse(triangle, 0, 0, triangle[0][0]);
12         return best;
13     }
14     //从[0,0]走到[x,y]路径只和为sum
15     public void traverse(int[][] triangle, int x, int y, int sum) {
16         if (x == triangle.length - 1) {
17             best = Math.min(best, sum);
18             return;
19         }
20         traverse(triangle, x + 1, y, sum + triangle[x + 1][y]);
21         traverse(triangle, x + 1, y + 1, sum + triangle[x + 1][y + 1]);
22     }
View Code

方法二:Divider & Conquer。复杂度仍然是2^n,每次走都有两个选择,并没有任何阻拦,所以仍然超时。

技术分享
 1 public int minimumTotal(int[][] triangle) {
 2         //traverse
 3         if (triangle == null || triangle.length == 0) {
 4             return -1;
 5         }
 6         return dividerConquer(triangle, 0, 0);
 7         
 8     }
 9     //从[x,y]出发,走到最底层,最短路径是多少
10     public int dividerConquer(int[][] triangle, int x, int y) {
11        if (x == triangle.length) {
12            return 0;
13        }
14        int left = dividerConquer(triangle, x + 1, y);
15        int right = dividerConquer(triangle, x + 1, y + 1);
16        return Math.min(left, right) + triangle[x][y];
17     }
View Code

方法三:观察Divider & Conquer方法,发现存在许多重复计算。所以这里存在很大的优化空间。假如我们把计算过的结果记忆,那么在此遍历到这个点的时候就不在往下分治。

    记忆化搜索,使用hash[i][j]来记录(i,j)到最底层的最短路径,在此遍历到的时候直接返回即可。由于每个节点就计算一次,所以这里复杂度变为 n^2。

技术分享
 1 public int minimumTotal(int[][] triangle) {
 2         //traverse
 3         if (triangle == null || triangle.length == 0) {
 4             return -1;
 5         }
 6         Integer[][] hash = new Integer[triangle.length][triangle.length];
 7         for (int i = 0; i < hash.length; i++) {
 8             for (int j = 0; j <= i; j++) {
 9                 hash[i][j] = Integer.MAX_VALUE;
10             } 
11         } 
12         return dividerConquer(triangle, 0, 0, hash);
13         
14     }
15     //从[x,y]出发,走到最底层,最短路径是多少
16     public int dividerConquer(int[][] triangle, int x, int y,Integer[][] hash) {
17        if (x == triangle.length) {
18            return 0;
19        }
20        if (hash[x][y] != Integer.MAX_VALUE) {
21            return hash[x][y];
22        }
23        int left = dividerConquer(triangle, x + 1, y, hash);
24        int right = dividerConquer(triangle, x + 1, y + 1, hash);
25        hash[x][y] = Math.min(left, right) + triangle[x][y];
26        return hash[x][y];
27     }
View Code

 方法四:多重循环实现DP。自上而下。需要特别注意的是,需要单独初始化边界。最左边只能从上边来。最右边只能从左上来。

技术分享
 1 public int minimumTotal(int[][] triangle) {
 2         //traverse
 3         if (triangle == null || triangle.length == 0) {
 4             return -1;
 5         }
 6         //dp[i][j]表示从(0,0)出发,到达(i,j)的最短路径
 7         int len = triangle.length;
 8         int[][] dp = new int[len][len];
 9         dp[0][0] = triangle[0][0];
10         for (int i = 1; i < len; i++) {
11             dp[i][0] = dp[i-1][0] + triangle[i][0];
12             dp[i][i] = dp[i -1][i - 1] + triangle[i][i];
13         }
14         for (int i = 1; i < len ; i++) {
15             for (int j = 1; j < i; j++) {
16                 dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle[i][j];
17             }
18         }
19         int result = Integer.MAX_VALUE;
20         for (int j = 0; j < len ;j++) {
21             result = Math.min(result, dp[len - 1][j]);
22         }
23         return result;
24     }
View Code

 

方法五:多重循环DP。自下而上

技术分享
 1 public int minimumTotal(int[][] triangle) {
 2         //traverse
 3         if (triangle == null || triangle.length == 0) {
 4             return -1;
 5         }
 6         //dp[i][j]表示(i,j)到最底层的最短路径
 7         int len = triangle.length;
 8         int [][] dp = new int[len][len];
 9         for (int j = 0; j < len; j++) {
10             dp[len - 1][j] = triangle[len - 1][j];
11         }
12         for (int i = len - 2; i >=0; i--) {
13             for (int j = 0; j <= i; j++) {
14                 dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle[i][j];
15             }
16         }
17         return dp[0][0];
18     }
View Code

 

以上是关于动态规划问题的主要内容,如果未能解决你的问题,请参考以下文章

算法动态规划 ⑤ ( LeetCode 63.不同路径 II | 问题分析 | 动态规划算法设计 | 代码示例 )

动态 Rstudio 代码片段

是否可以动态编译和执行 C# 代码片段?

算法动态规划 ③ ( LeetCode 62.不同路径 | 问题分析 | 自顶向下的动态规划 | 自底向上的动态规划 )

算法动态规划 ③ ( LeetCode 62.不同路径 | 问题分析 | 自顶向下的动态规划 | 自底向上的动态规划 )

Python之动态规划算法