• 63、不同路径 II


    题目:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
      网格中的障碍物和空位置分别用 1 和 0 来表示。
      说明:m 和 n 的值均不超过 100。
    在这里插入图片描述

    动态规划

      我们用 f(i,j) 来表示从坐标 (0,0) 到坐标 (i,j) 的路径总数,u(i,j) 表示坐标(i,j) 是否可行,如果坐标 (i,j) 有障碍物,u(i,j)=0,否则 u(i,j)=1。

      因为「机器人每次只能向下或者向右移动一步」,所以从坐标(0,0) 到坐标 (i,j) 的路径总数的值只取决于从坐标 (0,0) 到坐标(i−1,j) 的路径总数和从坐标 (0,0) 到坐标 (i,j−1) 的路径总数,即 f(i,j) 只能通过 f(i−1,j) 和 f(i,j−1) 转移得到。当坐标 (i,j) 本身有障碍的时候,任何路径都到到不了 f(i,j),此时 f(i, j) = 0;下面我们来讨论坐标 (i, j)没有障碍的情况:如果坐标 (i - 1, j)没有障碍,那么就意味着从坐标 (i−1,j) 可以走到 (i, j),即 (i - 1, j) 位置对 f(i, j) 的贡献为 f(i - 1, j),同理,当坐标 (i, j - 1)没有障碍的时候,(i, j - 1) 位置对 f(i, j)的贡献为 f(i, j - 1)。综上所述,我们可以得到这样的动态规划转移方程:
    在这里插入图片描述

    public int uniquePathsWithObstacles2(int[][] obstacleGrid) {
            int n = obstacleGrid.length,m = obstacleGrid[0].length;
            int[][] f = new int[n][m];
            
            //起始点要没有障碍物则它到相点的路径都为1
            f[0][0] = obstacleGrid[0][0] == 0 ? 1 : 0;
            for (int i = 0; i < n; ++i) {
                for (int j = 0; j < m; ++j) {
                    //最后一位为1即障碍物,则路径为0
                    if(obstacleGrid[i][j]==1){
                        continue;
                    }
                    //计算从起始点到目标点左侧点的所有路径数
                    if (j>0&&obstacleGrid[i][j-1] == 0) {
                        f[i][j] += f[i][j-1];
                    }else {
                        f[i][j] += 0;
                    }
                    //计算从起始点到目标点上侧点的所有路径数
                    if (i>0&&obstacleGrid[i-1][j] == 0) {
                        f[i][j] += f[i-1][j];
                    }else {
                        f[i][j] += 0;
                    }
                }
            }
    
            return f[n-1][m - 1];
        }
    

    时间复杂度:O(nm),其中 n 为网格的行数,m 为网格的列数。我们只需要遍历所有网格一次即可。
    空间复杂度:O(mn)
      对给定的数组进行遍历,若坐标点上的数为1即有障碍物,则到该点的路径总数为一定0;若坐标点上的数为0即无障碍物,则到该点的路径总数为分别到其左侧点和上侧点路径总数的和,当然到其左侧点或上侧点的路径总数可能为0。最后返回右下角点上的路径总数即为所求。

      很显然上面的解法是个时间复杂度为 O(nm)并且空间复杂度也是 O(nm)的实现,由于这里 f(i, j)只与 f(i - 1, j) 和 f(i, j - 1) 相关,我们可以运用「滚动数组思想」把空间复杂度优化称 O(m)。

    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
            int n = obstacleGrid.length, m = obstacleGrid[0].length;
            int[] f = new int[m];
    
            f[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) {
                        f[j] = 0;
                        continue;
                    }
                    if (j - 1 >= 0 && obstacleGrid[i][j - 1] == 0) {
                        f[j] += f[j - 1];
                    }
                }
            }
    
            return f[m - 1];
        }
    

    时间复杂度:O(nm),其中 n 为网格的行数,m 为网格的列数。我们只需要遍历所有网格一次即可。
    空间复杂度:O(m)。利用滚动数组优化,我们可以只用 O(m) 大小的空间来记录当前行的 f 值。
      这里的所谓滚动数组法,即直接用一个与原二维坐标数组列长相等的一维数组来存储到每列各个元素的路径总数,其实思路和上面的方法一样只不过是用一个数组来存储左侧和上侧路径总数,比如当 i 不变随着 j 的变换是在计算从左侧到每个坐标点的路径总数,当 j 不变 i 变换是就是在计算从上方到每个坐标点的路径总数,然后与前面计算的对应的从左侧的路径数相加存储在一维数组中即可,同样的当坐标点为1 时,令到该坐标点的路径总数为 0 ,即一维数组中暂时令该列下标对应的点值为0,之所以说是暂时是因为一列还有别的坐标点可能不为1,最后的值会更新,总体思路和上述方法大同小异。

    参考:
    Leetcode官方题解

  • 相关阅读:
    【秒懂Java】【01_初识Java】04_学习资料
    【秒懂Java】【01_初识Java】03_Java简介
    【秒懂Java】【01_初识Java】02_软件开发
    【秒懂Java】【01_初识Java】01_编程语言
    Apriori算法
    Java并发编程--ThreadLocal内存泄漏原因
    Java并发编程--锁
    Java并发编程--wait/notify/notifyAll 方法的使用
    Java并发编程--线程的生命周期
    Java虚拟机--垃圾收集器--G1收集器
  • 原文地址:https://www.cnblogs.com/firecode7/p/16120443.html
Copyright © 2020-2023  润新知