【LeetCode】63. Unique Paths II 解题报告(Python & C++)
作者: 负雪明烛
id: fuxuemingzhu
个人博客: http://fuxuemingzhu.cn/
题目地址:https://leetcode.com/problems/unique-paths-ii/description/
题目描述:
A robot is located at the top-left corner of a m x n grid (marked ‘Start’ in the diagram below).
The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked ‘Finish’ in the diagram below).
Now consider if some obstacles are added to the grids. How many unique paths would there be?

An obstacle and empty space is marked as 1 and 0 respectively in the grid.
Note: m and n will be at most 100.
Example 1:
Input:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
Output: 2
Explanation:
There is one obstacle in the middle of the 3x3 grid above.
There are two ways to reach the bottom-right corner:
1. Right -> Right -> Down -> Down
2. Down -> Down -> Right -> Right
题目大意
给出了一个m * n的地图,上面有个机器人位于左上角,现在他想到达右下角。但是这个地图某些位置可能有障碍物。它每次只能向右边或者下边走一步,问能到达右下角的方式有多少种。
解题方法
方法一:记忆化搜索
这个题是62. Unique Paths的翻版,加上了障碍物一项。同样的分析:
到达某个位置的次数怎么计算?可以想到是到达这个位置上面的位置的次数+到达坐标的次数。
注意了,有障碍物!其实有障碍物的话可以直接返回这个点的可能次数是0.为什么?因为每个点能从左或者上边过来,如果某个方向是障碍物的话,那么从障碍物过来的可能性是0.
如果递归到边界以外的话,可能性也是0,这样给第一行或者第一列提供了终止条件。如果是左上角开始的位置,那么方式只有1种,代表了起始条件。
另外使用了记忆化数组保存已经走过位置的次数,可以加快运算。
时间复杂度是O(m * n),空间复杂度是O(m * n)。这个方法AC了,但是竟然没进排名。。
class Solution(object):
def uniquePathsWithObstacles(self, obstacleGrid):
"""
:type obstacleGrid: List[List[int]]
:rtype: int
"""
m, n = len(obstacleGrid), len(obstacleGrid[0])
memo = [[0] * n for _ in range(m)]
return self.dfs(m - 1, n - 1, obstacleGrid, memo)
def dfs(self, m, n, obstacleGrid, memo): # methods of postion m, n
if obstacleGrid[m][n] == 1:
return 0
if m < 0 or n < 0:
return 0
if m == n == 0:
return 1
if memo[m][n]:
return memo[m][n]
up = self.dfs(m - 1, n, obstacleGrid, memo)
left = self.dfs(m, n - 1, obstacleGrid, memo)
memo[m][n] = up + left
return memo[m][n]
方法二:动态规划
看到上面记忆化搜索的方法就知道这个题同样可以使用动态规划解决。第一行第一列的所有方式只有1种,到达其他位置的方式是这个位置上面 + 这个位置左边用DP的话,注意需要判断某个位置是不是有障碍物,如果有障碍物,那么到达这个地方的方法是0。总体思路和上面记忆化搜索差不多。
时间复杂度是O(m * n),空间复杂度是O(m * n)。
动态规划写了三种方法,不停地优化,最后打败了100%的提交。
第一种,就是对上面的记忆化搜索的修改,给整个数组最上面和最左边加了一层边界,其含义是到达这个位置的方法是0,然后这么做的目的是防止下标越界。
这个做法超过了69%的提交。
class Solution(object):
def uniquePathsWithObstacles(self, obstacleGrid):
"""
:type obstacleGrid: List[List[int]]
:rtype: int
"""
m, n = len(obstacleGrid), len(obstacleGrid[0])
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if obstacleGrid[i - 1][j - 1] == 1:
dp[i][j] = 0
else:
if i == j == 1:
dp[i][j] = 1
else:
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
return dp[m][n]
上面的方法不好,因为修改了边界之后和原始的地图对应关系被打乱了,很容易出错。所以有了第二个版本,不要添加边界,直接判断是不是到达了最上面一行或者最左边一行,如果不是第一行那么加上上面行的数值,如果不是第一列那么加上第一列的数值。
这个做法打败了100%的提交。
class Solution(object):
def uniquePathsWithObstacles(self, obstacleGrid):
"""
:type obstacleGrid: List[List[int]]
:rtype: int
"""
m, n = len(obstacleGrid), len(obstacleGrid[0])
dp = [[0] * n for _ in range(m)]
if obstacleGrid[0][0] == 0:
dp[0][0] = 1
for i in range(m):
for j in range(n):
if obstacleGrid[i][j] == 1:
dp[i][j] = 0
else:
if i != 0:
dp[i][j] += dp[i - 1][j]
if j != 0:
dp[i][j] += dp[i][j - 1]
return dp[m - 1][n - 1]
第三个版本,利用Python语言特性来减少判断次数。我们遍历的方向是第一行从左到右,然后再第二行从左到右的方式进行的,这样如果把dp全部初始化成了0,那么当计算第一行的时候dp[-1][j]实际上就是最后一行的dp,也就是0.同样的,dp[i][-1]实际上是最后一列的dp,但是还没遍历到过,所以也是0.总之,虽然dp数组在计算第一行和第一列的时候用到了最后一行最后一列的dp数据,但是由于还没有遍历到,那么dp数组实际上是0,所以完全可以省去判断。这种方式对于C++和Java不能进行负数索引的不能用。
这个做法打败了100%的提交。
class Solution(object):
def uniquePathsWithObstacles(self, obstacleGrid):
"""
:type obstacleGrid: List[List[int]]
:rtype: int
"""
m, n = len(obstacleGrid), len(obstacleGrid[0])
dp = [[0] * n for _ in range(m)]
if obstacleGrid[0][0] == 0:
dp[0][0] = 1
for i in range(m):
for j in range(n):
if obstacleGrid[i][j] == 0:
if i == j == 0:
continue
else:
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
return dp[m - 1][n - 1]
上面两个做法可以看出,这个题的规模确实很小,总运行时间只有20ms,就能打败100%。哪怕少了一个判断,就能超出一部分提交。
二刷的时候,使用动态规划,C++代码如下:
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
const int M = obstacleGrid.size(), N = obstacleGrid[0].size();
vector<vector<int>> dp(M + 1, vector<int>(N + 1, 0));
if (obstacleGrid[0][0] != 1)
dp[1][1] = 1;
for (int i = 1; i < M + 1; ++i) {
for (int j = 1; j < N + 1; ++j) {
if (i == 1 && j == 1) continue;
if (obstacleGrid[i - 1][j - 1] != 1)
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[M][N];
}
};
日期
2018 年 10 月 18 日 —— 做梦都在科研
2018 年 12 月 29 日 —— 2018年剩余电量不足1%
【LeetCode】63. Unique Paths II 解题报告(Python & C++)的更多相关文章
- LeetCode: Unique Paths II 解题报告
Unique Paths II Total Accepted: 31019 Total Submissions: 110866My Submissions Question Solution Fol ...
- LeetCode 63. Unique Paths II不同路径 II (C++/Java)
题目: A robot is located at the top-left corner of a m x n grid (marked 'Start' in the diagram below). ...
- [LeetCode] 63. Unique Paths II 不同的路径之二
A robot is located at the top-left corner of a m x n grid (marked 'Start' in the diagram below). The ...
- leetcode 63. Unique Paths II
Follow up for "Unique Paths": Now consider if some obstacles are added to the grids. How m ...
- LeetCode: 63. Unique Paths II(Medium)
1. 原题链接 https://leetcode.com/problems/unique-paths-ii/description/
- [leetcode] 63. Unique Paths II (medium)
原题 思路: 用到dp的思想,到row,col点路径数量 : path[row][col]=path[row][col-1]+path[row-1][col]; 遍历row*col,如果map[row ...
- leetcode 62. Unique Paths 、63. Unique Paths II
62. Unique Paths class Solution { public: int uniquePaths(int m, int n) { || n <= ) ; vector<v ...
- 【LeetCode】63. Unique Paths II
Unique Paths II Follow up for "Unique Paths": Now consider if some obstacles are added to ...
- [Leetcode Week12]Unique Paths II
Unique Paths II 题解 原创文章,拒绝转载 题目来源:https://leetcode.com/problems/unique-paths-ii/description/ Descrip ...
随机推荐
- R语言与医学统计图形-【30】流行病学数据可视化
sjPlot包适用于社会科学.流行病学中调查数据可视化,且能和SPSS数据无缝对接(流行病学问卷调查录入Epidata软件后,都会转成SPSS格式或EXCEL格式保存). 辅助包sjmisc进行数据转 ...
- 爬虫动态渲染页面爬取之selenium驱动chrome浏览器的使用
Selenium是一个用于Web应用程序测试的工具.Selenium测试直接运行在浏览器中,就像真正的用户在操作一样,可以用其进行网页动态渲染页面的爬取. 支持的浏览器包括IE(7, 8, 9, 10 ...
- 48-Merge Sorted Array
$88. Merge Sorted Array My Submissions QuestionEditorial Solution Total Accepted: 98885 Total Submis ...
- Scrapy-Splash的安装和使用
Scrapy-Splash是一个Scrapy中支持JavaScript渲染的工具. Scrapy-Splash的安装分为两部分.一个是Splash服务的安装,具体是通过Docker,安装之后,会启动一 ...
- Hive(十二)【调优】
目录 1.Fetch抓取 2.本地模式 3.表的优化 3.1大小表join 3.2大表Join大表 3.3map join 3.4group By 3.5 count(distinct) 3.6笛卡尔 ...
- 【二分答案】CF1613 C. Poisoned Dagger
题目:Problem - C - Codeforces 本题的优解是二分答案,但我其实不会二分,本质是用了两个指针作为边界,然后不断对半缩小范围来快速确定答案. 神奇的二分法 代码: #include ...
- STM32一些特殊引脚做IO使用的注意事项
1 PC13.PC14.PC15的使用 这三个引脚与RTC复用,<STM32参考手册>中这样描述: PC13 PC14 PC15需要将VBAT与VDD连接,实测采用以下程序驱动4个74HC ...
- Mysql不锁表备份之Xtrabackup的备份与恢复
一.Xtrabackup介绍 MySQL冷备.热备.mysqldump都无法实现对数据库进行增量备份.如果数据量较大我们每天进行完整备份不仅耗时且影响性能.而Percona-Xtrabackup就是为 ...
- canal安装与使用
安装 alpha的版本不是稳定的版本 wget https://github.com/alibaba/canal/releases/download/canal-1.1.4/canal.deploye ...
- class.getName()和class.getSimpleName()的区别
根据API中的定义: Class.getName():以String的形式,返回Class对象的"实体"名称: Class.getSimpleName():获取源代码中给出的&qu ...