二维数组4:二维数组选不同路径

Posted 纵横千里,捭阖四方

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二维数组4:二维数组选不同路径相关的知识,希望对你有一定的参考价值。

​LeetCode62和63题也是两道紧密相关的题。

一.LeetCode62:机器人路径

62题的要求是:一个机器人在m×n大小的地图的左上角(起点)。机器人每次向下或向右移动。机器人要到达地图的右下角(终点)。可以有多少种不同的路径从起点走到终点?,备注:m和n小于等于100,并保证计算结果在int范围内。

63题的要求是在上面的基础上,如果中间某个位置存在障碍物,那一共有多少种路径。

思路:这个题是典型的动态规划。后面我们还会分析,现在先不想这么多,直接看怎么做:

如下图所示,从起点开始的每一个位置,要么向右,要么向下,图中分别用两个红点表示。每一种都将导致剩下的区间的减少了一行或者一列,形成两个不同的区间。而每个区间都可以继续以红点为起点继续上述操作,所以这就是一个递归的过程。

从上面的分析我们可以看到,每个位置都有两种情况,一种情况导致接下来的区间是[m-1,n],一个区间是[m,n-1]。所以每个位置的2种加起来就行了,剩下的交给下一个位置来递归处理。代码如下:

public class Solution {
 
   public int uniquePaths (int m, int n) {
     return search(m,n);
       
  }
   public int search(int m,int n){
         if(m==1 || n==1){
           return 1;
      }
      return search(m-1,n)+search(m,n-1);
  }
}

 上面这个过程,我们也可以用二叉树表示出来:

 这与二叉树的递归遍历要考虑左右子树,本质上是一样的。

从上面这个树也可以看到存在大量的重复,例如(1,1),(2,1),(1,2)等等,因此还是有优化的空间的,方法就是定义一个数组dp[m][n],算好的就记录在数组中,从而提高计算速度,当然此时空间复杂度为O(M*N)。

代码如下:

class Solution {    public int uniquePaths(int m, int n) {        int[][] dp = new int[m][n];        //第一行都赋予 1        for(int i = 0; i < m; ++i) {            dp[i][0] = 1;        }        //第一列都赋予 1        for(int j = 0; j < n; ++j) {            dp[0][j] = 1;        }        //两个for循环推导,对于(i,j)来说,只能由上方或者左方转移过来        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];    }} 

这里的缓存空间使用的是二维数组,这个占空间太大了,能否进一步优化呢?

我们再看一下计算流程:

在上图中:

最终结果15=5+10

5=4+1

10=6+4

....

也就是说dp[i][j]的值来自于:

左侧的dp[i - 1][j]和上方的dp[i][j - 1]

二维矩阵其他位置元素其实与最终结果无关,所以我们就可以从起点1开始这么计算了:

dp[j] = dp[j] + dp[j - 1]

此时代码就可以这么写了:

class Solution {    public int uniquePaths(int m, int n) {        int[] dp = new int[n];        Arrays.fill(dp, 1);        for(int i = 1; i < m; ++i) {            for(int j = 1; j < n; ++j) {                //等式右边的 dp[j]是上一次计算后的,加上左边的dp[j-1]即为当前结果                dp[j] = dp[j] + dp[j - 1];            }        }        return dp[n - 1];    }   }

那这是什么呢?本质上就是斐波那契数列,不信的话,先看斐波那契数列的非递归代码:

public class Solution {    public int Fibonacci(int n) {        if(n==0)return 0;        if(n==1)return 1;                int first = 0;        int second = 1;        int fibn = 0;        for(int i = 2; i <= n; i++) {            fibn = first + second;            first = second;            second = fibn;        }        return fibn;    }}

虽然略有差异,但是不是极度相似?

 二.leetcode 63 假如中间存在一个障碍物怎么办?

这个处理方法不算复杂,假如没有障碍物的格子标记为0,有障碍物的标记为1,那么执行的时候如果当前位置dp[i][j]==1时,直接跳过就行了。此时我们可以在上面的优化版的基础上这么写:

class Solution {    public int uniquePathsWithObstacles(int[][] obstacleGrid) {        int n = obstacleGrid.length, m = obstacleGrid[0].length;        int[] dp = new int[m];        dp[0] = obstacleGrid[0][0] == 0 ? 1 : 0;        for (int i = 0; i < n; ++i) {            for (int j = 0; j < m; ++j) {                if (obstacleGrid[i][j] == 1) {                    dp[j] = 0;                    continue;                }                if (j - 1 >= 0 && obstacleGrid[i][j - 1] == 0) {                    dp[j] += dp[j - 1];                }            }        }                return dp[m - 1];    }} 

如果面试直接考63题,估计我们会崩溃,但是如果我们先将62题研究清楚了,63题是不是就很简单了?

三、再折腾一下

我们前面说过,面试算法最喜欢的是换换条件不断折腾,那这里我们能否再当一次面试官,假如有k个障碍物,k<min(m,n),此时该怎处理呢?

其实,从代码的角度,我们什么都不用干,上面的代码就能执行,因为问题的关键在于传入进来的obstacleGrid数组包含多个元素1罢了,我们在双层循环里的这个判断足以处理。

 if (obstacleGrid[i][j] == 1) {      dp[j] = 0;       continue; }

这也是我们一直说的,一个方法可以解决大量的问题。一个问题改改条件就能造出大量的题目,我们下期继续折腾!

以上是关于二维数组4:二维数组选不同路径的主要内容,如果未能解决你的问题,请参考以下文章

二维数组怎么赋值

将表示神经网络的锯齿状数组转换为表示其神经路径 C# 的二维数组

LeetCode动态规划#02图解不同路径I + II(首次涉及二维dp数组,)

回溯法计算二维数组最短路径

二维数组的回波值

快速排序算法实现(二维数组)