动态规划是一种通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法,常常适用于有重叠子问题和最优子结构性质的问题,并且记录所有子问题的结果,因此动态规划方法所耗时间往往远少于朴素的穷举解法。
动态规划有自底向上和自顶向下两种解决问题的方式。自顶向下即记忆化递归,自底向上就是递推。
使用动态规划解决的问题有个明显的特点:无后效性,一旦一个子问题的求解得到结果,以后的计算过程就不会修改它。
我们以一个简单的路径问题,来解锁下动态规划的解题思路
1.一个棋子位于一个 m x n 棋盘的左上角 ,棋子每次只能向下或者向右移动一步。如果将棋子从左上角移动到右下角,
问总共有多少条不同的路径?
一般人在没有了解动态规划之前,处理这种问题只能靠穷举所有路线得到结果。
但是穷举可能会漏会重,在mxn的数字比较大时,耗时很长且非常容易出错。
那动态规划如何解决这个问题呢?我们先画个图
s | |||
e |
如上图所示,棋子要从棋盘的s位置,移动到e,而且只能向下或向右移动,每次移动一步。
那我们返回来思考,假如棋子已经移动到e,那么他是从哪里过来的呢?
显而易见,只能从左边格子或者上边格子,即两个我们标蓝色的格子。
那我们可以定义f(i,j)为从s移动到e的所有路径的总和。那么f(i,j)=f(i-1,j)+f(i,j-1)
这就是我们的动态转移方程。
接下来上代码
class Solution { public int uniquePaths(int m, int n) { //定义dp数组 int[][] dp = new int[m ][n ]; //第一行,第一列的位置,都只有唯一一种路径 for (int i = 0; i < m; i++) { dp[i][0] = 1; } for (int j = 0; j < n; j++) { dp[0][j] = 1; } for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { //通过子问题的结果,推导出上一层问题的结果 dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; } } return dp[m-1][n-1]; } }
2.最小路径和,给定一个包含非负整数的 m x n
棋盘 ,棋盘的每个格子都有一个数字,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
1 | 3 | 1 |
1 | 5 | 1 |
4 | 2 | 1 |
这个题目跟上一个非常类似,只是不再求路径总数,而是要求一个最小的路径和。
其实思路是一样的,我们假设我们已经达到右下角,那么来路无非还是两个左边的格子和上边的格子,
假设到达这两个格子的最小路径分别是left,top。
为了求最小路径,我们需要比较下left和top的大小,然后取较小的哪一个,然后再加上当前格子的值,就是右下角的最小路径和
假设f(i,j)是位置i,j的最小路径和,nums[i,j]是位置i,j的数字,那么我们就得到了动态转移方程:
f(i,j)=min{f(i,j-1),f(i-1,j)}+nums[i,j]
class Solution { public int minPathSum(int[][] grid) { int m = grid.length; int n = grid[0].length; int[][] dp = new int[m][n]; //dp第一列等于grid第一列的累计和 int total1 = 0; for (int i = 0; i < m; i++) { total1 += grid[i][0]; dp[i][0] = total1; } int total2 = 0; //dp第一行等于grid第一行的累计和 for (int j = 0; j < n; j++) { total2 += grid[0][j]; dp[0][j] = total2; } for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]; } } return dp[m - 1][n - 1]; } }