在没有接触过动态规划之前,是这样思考这个题的。
首先想到的就是递归中青蛙跳台阶的问题,自然想到递归。就使用原函数 int uniquePaths(int m, int n)
- 函数的作用,遵循规则的情况下,有多少中途径到达坐标为(m-1,n-1)的位置
- 递归终止条件:原点(m=n=1),函数返回1,如果m==0||n==0也是返回1(只有一种)
- 递归关系:uniquePaths(m,n)=uniquePaths(m-1,n)+uniquePaths(m,n-1)
代码完成如下:
class Solution { public int uniquePaths(int m, int n) { if(m==1||n==1) {return 1;} return uniquePaths(m-1,n)+uniquePaths(m,n-1); } }
然而超时了。。。
递归有重复计算的问题,在很多格子中,明明曾经已经计算过结果,但是碰到仍然向后继续递归了:
加一个数组,用于保存已经计算的结果
class Solution { int[][] dp = new int[100][100]; public int uniquePaths(int m, int n) { return cycle(m-1,n-1); } public int cycle(int i,int j){ if(i == 0 || j == 0) return 1; if(dp[i][j] != 0) return dp[i][j]; dp[i][j] = cycle(i-1,j)+cycle(i,j-1); return dp[i][j]; } }
通过接下来看动态规划的算法。
动态规划是正着推,而且有一个用于存放结果的一维/二维数组,大部分问题是二维的。
首先需要确认该问题是否符合动态规划解题要求
动态规划适合解决的问题模型符合 "一个模型三个特性"
一个模型可以概括为:多阶段决策最优解模型; 本题目第一行是已经定好了都是1,
在第二行的时候,每个方格的值都是该方格左边的格和该方格上边的格的总和,以此类推,满足.
- 特性1:最优子结构;每个阶段的状态或值都是通过前面阶段的状态或值推导出来的,满足.
- 特性2:无后效性;每个阶段的状态值一旦确定之后,是不受后面阶段状态值所影响的,满足.
- 特性3:重复子问题;从递归解法中就能看出来有重复子问题的计算,满足.
接下来,该分析如何套用动态规划解题步骤
首先,定义 二维数组保存路径条数,按照递归中分析得出第一行和第一列都是1,所以二维数组直接定义成如此
然后分阶段阶段,每一横行的处理就是一个阶段,通过上一行就能推导出下一行的状态值
在每一个阶段中, 同一行中的方格,是其左边方格的值加上一行方格的总和
最后返回二维数组最后一个元素的值即可.
class Solution { public int uniquePaths(int m, int n) { int [][] dp = new int[m+1][n+1]; //列 for(int i = 1;i){ //行 for(int j = 1;j ){ if(i == 1 && j == 1){ dp[1][1] = 1; }else{ //状态转移方程 dp[i][j] = dp[i-1][j] + dp[i][j-1]; } } } return dp[m][n]; } }
这样的时间复杂度和空间复杂度都是om*n
有优化的办法,把空间复杂度优化为O2n或者On
优化一:将一个表优化成了表中我们需要的两行
优化二:在优化一的基础上,将两行优化成了我们需要的当前行,因为cur未更新前保存的结果是上一行的结果
另外,这道题其实直接排列组合就能做: