动态规划问题
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 }
方法二: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 }
方法三:观察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 }
方法四:多重循环实现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 }
方法五:多重循环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 }
以上是关于动态规划问题的主要内容,如果未能解决你的问题,请参考以下文章
算法动态规划 ⑤ ( LeetCode 63.不同路径 II | 问题分析 | 动态规划算法设计 | 代码示例 )
算法动态规划 ③ ( LeetCode 62.不同路径 | 问题分析 | 自顶向下的动态规划 | 自底向上的动态规划 )
算法动态规划 ③ ( LeetCode 62.不同路径 | 问题分析 | 自顶向下的动态规划 | 自底向上的动态规划 )