[Leetcode刷题总结系列][Dynamic Programming]62. Unique Paths

62. Unique Paths

A robot is located at the top-left corner of a m*n grid.

The robot only moves either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid.

How many unique paths are there?

我们知道一道题要想满足可用Dynamic Programming解决,需要具有如下特性:

1. 问题可以被划分为子问题求解

2. 问题存在最优子结构,也就是划分为子问题后,大问题的最优解可以由小问题的最优解得到

3. 无后效性,也就是说第i+1个子问题的解只与前i个问题有关,而与其后的问题无关

4. 大问题所需要的小问题之间有相互重叠的情况(可用cache小问题答案的方法解决)


那么再看本题,显然是具有最优子结构的。因为到达点[i, j]的不同路线数可以通过到达点[i, j-1]和点[i-1, j]的不同路线数得到。且也满足无后效性的性质。同时,小问题之间还具有相互重叠的情况(求解[i, j] 需要[i, j-1]和[i-1, j]进而需要[i-1, j-1],[i, j-2]等等,而[i, j-1]和[i, j-1]又需要[i-1, j-1],[i, j-2]等)

从而得出本题的状态方程:

 0 if i == 0 || j == 0 (don't exist if one of the dimension is 0)

Num[i][j] = 1  if i ==1 && j == 1 

 Num[i-1][j] + Num[i][j-1]  others


Java中数组的起始索引是0,显然这个问题中当任一维度是0时不存在意义。那么我们可以将Num[0][j]和Num[i][0]全部赋值为0来避免机器人在原本在网格边界上时需要的判断,因为此种情况下显然上面的网格或左面的网格是不存在的,不加判断的话就会引起arrayindexoutofboundary的异常。

根据上述分析,代码如下:

这段代码的时间复杂度和空间复杂度都是O(mn),因为我们需要扫描整个网格并且将结果存在m*n大小的二维数组中。

public class Solution {
    public int uniquePaths(int m, int n) {
        /*
        Dynamic programming
        Bottom-up approach
        Let Num[i][j] represent the number of different paths to a given point [i, j] in the graph.
        Since any point [i, j] in the graph can only accessed by the point [i, j-1] or [i-1, j], so Num[i][j] = Num[i-1,j] + Num[i, j-1].
        Since Array indexes in java starts at 0, and we must have at least 1 point in the graph normally, as initialization we can let the values of all the points with i = 0 or j = 0 to be 0 and let Num[i][j] = 1. In this way we bypass the boundary condition where the robot is on the [1, j] or [i, 1] points, in which we have to judge the index of points to avoid array index out of boundary condition.
        This algorithm need O(n^2) time and O(n^2) space
        */
        
        if (m == 1 || n == 1){
            return 1;
        }
        
        int[][] Num = new int[m+1][n+1];
        for (int i = 0; i <= m; i ++){
            for (int j = 0; j <= n; j ++){
                if (i == 0 || j == 0){
                    Num[i][j] = 0;
                }
                
                else if (i == 1 && j == 1){
                    Num[i][j] = 1;
                }
                
                else {
                    Num[i][j] = Num[i-1][j] + Num[i][j-1];
                }
            }
        }
        
        return Num[m][n];
    }
}


进一步优化:

因为我们在这道题目中不需要重构问题解决的步骤,所以我们只需要知道最终的结果即可,也就是Num[i][j]。那么使用O(mn)的空间来存每一步小问题的解就是没有必要的。我们可以将此问题需要的空间优化至O(n),既只使用大小为n的一维数组储存结果即可。根据此题的状态方程(既Num[i][j]与Num[i-1][j]和Num[i][j-1]有关)。

因为Num[i][j] = Num[i][j-1] + Num[i-1][j],所以我们可用一个一位数组改变这个方程为Num[j] = Num[j] + Num[j-1],这样我们永远不会在使用完某一个值之前将其覆盖掉。

根据以上分析,我们应当从左向右来覆盖这个一维数组。


对应的代码如下:

public class Solution {
    public int uniquePaths(int m, int n) {
        /*
        Dynamic Programming
        Bottom-up approach
        If we use Num[n] to denote the number of paths and we fill this form from left to right, we will never rewrite a value before we make use of it.
        O(mn) time and O(n) space
        */
        if (m == 0 || n == 0){
            return 0;
        }
        
        int[] Num = new int[n];
        for (int i = 0; i < m; i ++){
            for (int j = 0; j < n; j++){
                if (i == 0 || j == 0){
                    Num[j] = 1;
                }
                
                else {
                    Num[j] += Num[j-1];
                }
            }
        }
        
        return Num[n-1];
        
    }
}
        


对于这道题目,我们甚至可以不用Dynamic Programming而改用数学方法求解:

根据此题题意,我们总共需要走m+n-2步:向右n-1步,向下m-1步。

而且这两者是互补的。既只要我们选定了哪几步向右走就相当于选定了哪几步向下走(选完向右走的步数之外剩下的步数向下走的概率为1),这其实也和组合中的C(n,m) = C (m-n, m)是相同的道理。

那么对于m*n大小的网格,我们总共的可能的路线数即为C(n-1, m+n-2)或C(m-1, m+n-2)

因此对于任意输入,我们只需要计算这个式子即可。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

耀凯考前突击大师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值