给你一个 m * n
的网格,其中每个单元格不是 0
(空)就是 1
(障碍物)。每一步,您都可以在空白单元格中上、下、左、右移动。
如果您 最多 可以消除 k
个障碍物,请找出从左上角 (0, 0)
到右下角 (m-1, n-1)
的最短路径,并返回通过该路径所需的步数。如果找不到这样的路径,则返回 -1
。
示例 1:
输入: grid = [[0,0,0],[1,1,0],[0,0,0],[0,1,1],[0,0,0]], k = 1
输出:6
解释:
不消除任何障碍的最短路径是 10。
消除位置 (3,2) 处的障碍后,最短路径是 6 。该路径是 (0,0) -> (0,1) -> (0,2) -> (1,2) -> (2,2) -> (3,2) -> (4,2)
.
示例 2:
输入:grid = [[0,1,1],[1,1,1],[1,0,0]], k = 1 输出:-1 解释:我们至少需要消除两个障碍才能找到这样的路径。
提示:
grid.length == m
grid[0].length == n
1 <= m, n <= 40
1 <= k <= m*n
grid[i][j]
是0
或1
grid[0][0] == grid[m-1][n-1] == 0
通过次数20,747提交次数54,460
# 初步思考:
1.面试中遇到这个类型的题目可以问哪些问题?
value of grid[i][j] 是只有 0 和 1 吗?
len(grid) 和 len(grid[0])的取值范围
容易忘记问的一个问题:终点和起点的值是多少?
2.解决这道题需要哪些数据结构和算法?
最短距离,一下子就想到bfs,因为一定是从中心扩散出去的,所以从点A到达点B的距离一定会是最短距离。既然是BFS,常常伴随着visited set。因为题目说可以四个方向走动,所以需要一个direction 数组,记录四个走动的方向。所以总结下来需要:queue, visited, direction array
# 做题中遇到的问题:
1.什么时候把第一个坐标点加入到visited 里面?
这个题目,在进入while q之前,假如已经把这个点放进visited里面,就不要在点被pop出来之后立马判断该点是否在visited里面。因为一定在。我这个错误已经犯了很多次了。正确答案的代码我试过了,就算是一开始不把第一个点放进visited,其他代码保持不变,还是能够ac通过的。所以我每次建立visited的时候,如果在while循环之前把第一个点放进了visited,就不要着急着在点被pop出来之后就里面判断该点是否在visited里面了。
2.visited应该存储什么值,为什么不能是(x,y)坐标,而是得多存储一个 remaining_moves的状态?
因为这个题目里面,举个例子,bfs到达点(0,3)的时候,有可能是通过 (0,1) -> (0,2) -> (0,3)到达的;同时也有可能是突破了障碍物,通过(1,0) -> (1,1) -> (2,1) -> (3,1) - > (3,0);所以虽然是同一个点,但是remaining_move和move的状态是完全不一样的。
3.为什么在写代码的时候,必须要新建一个变量 new_remaining_move = remaining_move - grid[i][j], 而不能直接写成 remaining_move = remaining_move - grid[i][j]?
博客链接:python: python中a+=b与a=a+b有什么区别_DinnerHowe的博客-优快云博客_python中,a+=b
这是一个很奇怪同时也很有意思的现象。我查了一下有可能是因为python的传值特点。在python这门语言里面 a+=b 和 a = a + b 是两个不同的结果。具体可以看看这个例子:
链接: http://stackoverflow.com/questions/6951792/python-a-b-not-the-same-as-a-a-b
总体上讲,a+=b是改变了a原始的值,而a=a+b是计算出a+b后,a在指向那个值。这个也跟a和b的类型有关。当a和b是int或者string不可改变的时候,二者效果一样。
>>> a=[1,2,3,4]
>>> b=a
>>> a+=[5]
>>> a,b
([1, 2, 3, 4, 5], [1, 2, 3, 4, 5])#a,b的“地址”一样
>>> a=[1,2,3,4]
>>> b=a
>>> a=a+[5]#a+[5]后有一个新地址,再使a指向这个新地址,因此a不再是以前的a,而b还是以前的那个a//b,所以b不变
>>> a,b
([1, 2, 3, 4, 5], [1, 2, 3, 4])
# 解题代码:
class Solution(object):
def shortestPath(self, grid, k):
# 又是最短路径:做好 bfs 的准备就可以了
# remaining_moves
# visited 如果已经走过了,就不要再走了
m = len(grid)
n = len(grid[0])
if k >= m + n - 2: return m + n -2
q = collections.deque()
q.append((0,(0,0,k))) # why do we need to keep track of both move and remaining moves?
visited = set()
visited.add((0,0,k)) # 这段代码删掉也没有问题,可有可无
dir = [(0,1),(0,-1),(1,0),(-1,0)]
while q:
move, (x,y,remaining_moves) = q.popleft()
if x == m- 1 and y == n -1:
return move
for dx, dy in dir:
new_x, new_y = dx + x, dy + y
if new_x >= 0 and new_x < m and new_y >= 0 and new_y < n and grid[new_x][new_y] not in visited:
new_remaining_moves = remaining_moves - grid[new_x][new_y] # python传值问题
# 这句话如果写成 remaining_moves = remaining_moves - grid[new_x][new_y]
# 程序会出错返回-1 而不是例题里面的 6
new_state = (new_x,new_y,new_remaining_moves)
if new_remaining_moves >= 0 and new_state not in visited:
visited.add(new_state)
q.append((move+1, new_state))
return -1 # 要记得最后如果没到达终点,返回 -1
# 时空复杂度:
时间复杂度:O(MN∗min(M+N,K))。
空间复杂度:O(MN∗min(M+N,K))。
此外,我们还可以对搜索空间进行优化。注意到题目中 k 的上限为 m * n,但考虑一条从 (0, 0) 向下走到 (m - 1, 0) 再向右走到 (m - 1, n - 1) 的路径,它经过了 m + n - 1 个位置,其中起点 (0, 0) 和终点 (m - 1, n - 1) 没有障碍物,那么这条路径上最多只会有 m + n - 3 个障碍物。因此我们可以将 k 的值设置为 m + n - 3 与其本身的较小值 min(k, m + n - 3),将广度优先搜索的时间复杂度从 O(MNK)O(MNK) 降低至 O(MN∗min(M+N,K))。
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/shortest-path-in-a-grid-with-obstacles-elimination/solution/wang-ge-zhong-de-zui-duan-lu-jing-by-leetcode-solu/