Python leetcode最短路径问题

网格中内有不同的数字,数字表示某种特性,求解从起点到终点所需的最小值。
该类问题常用广度优先搜索方法BFS,优化可采用双向BFS,另外也可以尝试使用DP。
该类型题可以总结出以下模板:

def bfs(nums):
    m = len(nums); n = len(nums[0])
    visited = {(0, 0)} # 记录已访问的方格
    q = [(0, 0, 1)]   # 括号里面前两个数字表示已访问的方格的坐标,第三个数表示到达当前方格所需要是操作步数
    direction = [(1, 0), (0, 1), (-1, 0), (0, -1)] # 搜索的方向
    for a, b, dis in q:
        if a == m - 1 and b == n - 1: return dis # 达到终点时返回dis
        for i, j in direction:
            a_new = a + i; b_new = b + j
            if 0<= a_new < m and 0 <= b_new < n and nums[a_new][b_new] == 0: # 判断满足条件时,将满足的方格加入到visited和q中,同时dis需要进行加一操作
                visited.add((a_new, b_new))
                q.append((a_new, b_new, dis + 1))
    return -1 # 以上for循环没有输出,说明得不到答案,执行到这里返回-1

另外一个版本:

def bfs(nums):
    m = len(nums); n = len(nums[0])
    visited = {(0, 0)} # 记录已访问的方格
    from collections import deque
    q = deque([(0, 0, 1)]) 
    direction = [(1, 0), (0, 1), (-1, 0), (0, -1)] 
    while q:
        for _ in range(len(q)):
            a, b, dis = q.popleft()
            if a == m - 1 and b == n - 1: return dis 
            for i, j in direction:
                a_new = a + i; b_new = b + j
                if 0<= a_new < m and 0 <= b_new < n and nums[a_new][b_new] == 0: 
                    visited.add((a_new, b_new))
                    q.append((a_new, b_new, dis + 1))
    return -1

二进制矩阵中的最短路径

在这里插入图片描述

# BFS
class Solution:
    def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:
        n = len(grid)
        if grid[0][0] or grid[n-1][n-1]:
            return -1
        q = [(0, 0, 1)]
        grid[0][0] = 1 # 这里直接将grid进行修改,替换了visited集合
        for i, j, d in q:
            if i == n-1 and j == n-1: return d
            for x, y in ((i-1,j-1),(i-1,j),(i-1,j+1),(i,j-1),(i,j+1),(i+1,j-1),(i+1,j),(i+1,j+1)):
                if 0 <= x < n and 0 <= y < n and not grid[x][y]:
                    grid[x][y] = 1
                    q.append((x, y, d+1))
        return -1

# BFS
class Solution:
    def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:
        n = len(grid)
        if n == 1: return 1
        if grid[0][0] == 1 or grid[-1][-1] == 1:
            return -1
        x = [-1, -1, -1, 0, 0, 1, 1, 1]
        y = [-1, 0, 1, -1, 1, -1, 0, 1]
        path = collections.deque([(0, 0, 1)])
        while path:
            for _ in range(len(path)):
                a, b, dis = path.popleft()
                if a == n - 1 and b == n - 1: return dis
                for i, j in zip(x, y):
                    new_a, new_b = a + i, b + j
                    if 0 <= new_a < n and 0 <= new_b < n and grid[new_a][new_b] == 0:
                        grid[new_a][new_b] = -1
                        path.append((new_a, new_b, dis + 1))
        return -1

以下为双向BFS求解,代码不同的地方就是设计了三个集合,start、end和next_start,每一次的for循环找到的可行路径加入到next_start中,最后将其赋值给start,最后根据start与end的长短,将其进行调换。需要注意的是,这里需要在最内部的for循环中判断if (new_a, new_b) in end: return dis

在这里插入图片描述

# 双向BFS
class Solution:
    def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:
        n = len(grid)
        if n == 1: return 1
        if grid[0][0] or grid[-1][-1]== 1: return -1
        tuple_offset = ((1, 0), (0, 1), (1, 1), (-1, 0), (0, -1), (1, -1), (-1, 1), (-1, -1))
        start = {(0, 0)}
        end = {(n - 1, n - 1)}
        dis = 1
        while start:
            dis += 1
            next_start = set()
            for x, y in start:
                for x_offset, y_offset in tuple_offset:
                    new_a, new_b = x + x_offset, y + y_offset
                    # 两端碰撞
                    if (new_a, new_b) in end: return dis
                    if 0 <= new_a < n and 0 <= new_b < n and grid[new_a][new_b] == 0:
                        grid[new_a][new_b] = -1
                        next_start.add((new_a, new_b))
            start = next_start
            if len(end) < len(start):
                start, end = end, start
        return -1

该题也可以采用动态规划的思路进行求解。
DP 解题思路(已通过所有测试样例) python3

import copy
class Solution:
    def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:
        # 逻辑是 dp[i][j] = arround_min(i,j) + 1
        dx = [-1, -1, -1, 0, 0, 1, 1, 1]
        dy = [-1, 0, 1, 1, -1, -1, 0, 1]
        dist = 0
        N    = len(grid)

        # 边界条件
        if grid[0][0] == 1 or grid[-1][-1] == 1:return -1
        if N == 1 :return 1
        # 将不可走路径设为inf
        for i in range(N):
            for j in range(N):
                if grid[i][j] == 0:
                    grid[i][j] = inf
                else: grid[i][j] = 'stop'
        grid[0][0] = 1
        # 定义一个arround_min函数
        def arround_min(i, j):
            _min = inf
            for k in range(8):
                x, y = i + dx[k], j + dy[k]
                if (0 <= x < N and 0 <= y < N and grid[x][y] != 'stop'):
                    _min = min(_min, grid[x][y])
            return _min
        #这里必须深拷贝,因为是二维数组
        gridnew = copy.deepcopy(grid)

        while(True):
            #终止条件
            if grid[-1][-1] != inf:
                return grid[-1][-1]

            #循环
            for j in range(N):
                for i in range(N):
                    if i == 0 and j == 0:
                        continue
                    if grid[i][j] != 'stop':
                        grid[i][j] = arround_min(i, j) + 1

            # 判断grid是否还有变化,若没有则证明无解
            if gridnew == grid:
                return -1
            gridnew = copy.deepcopy(grid)

穿过迷宫的最少移动次数

在这里插入图片描述
注意:第一、二条规则是指蛇可以进行平移(当不存在障碍物的时候)

该题与上一题不同的是,本题没有特定的dircetion,需要根据周围障碍物的情况而进行方向的选择,因此需要进行枚举,代码如下:

class Solution:
    def minimumMoves(self, grid: List[List[int]]) -> int:
        n = len(grid)
        queue = [(0, 0, 0, 1, 0)]
        visit = {(0, 0, 0, 1)}
        for a1, b1, a2, b2, step in queue:
            if (a1, b1, a2, b2) == (n - 1, n - 2, n - 1, n -1): return step
            if a1 + 1 < n and a2 + 1 < n and grid[a1 + 1][b1] == grid[a2 + 1][b2] == 0:
                if (a1 + 1, b1, a2 + 1, b2) not in visit:
                    visit.add((a1 + 1, b1, a2 + 1, b2))
                    queue.append((a1 + 1, b1, a2 + 1, b2, step + 1))
                if a1 == a2 and (a1, b1, a1 + 1, b1) not in visit:
                    visit.add((a1, b1, a1 + 1, b1))
                    queue.append((a1, b1, a1 + 1, b1, step + 1))
            if b1 + 1 < n and b2 + 1 < n and grid[a1][b1 + 1] == grid[a2][b2 + 1] == 0:
                if (a1, b1 + 1, a2, b2 + 1) not in visit:
                    visit.add((a1, b1 + 1, a2, b2 + 1))
                    queue.append((a1, b1 + 1, a2, b2 + 1, step + 1))
                if b1 == b2 and (a1, b1, a1, b2 + 1) not in visit:
                    visit.add((a1, b1, a1, b2 + 1))
                    queue.append((a1, b1, a1, b2 + 1, step + 1))
        return -1

网格中的最短路径

在这里插入图片描述
该题由于可以对障碍物进行清除,因此不能在原有的数组中进行修改,即需要设计一个visited数组已表示已访问的网格。

讲解

class Solution:
    def shortestPath(self, grid: List[List[int]], k: int) -> int:
        m, n = len(grid), len(grid[0])
        if k >= m + n - 3: return m + n - 2
        q = [(0, 0, 0, 0)]
        visited = {(0, 0, 0)}
        direct = [(1, 0), (0, 1), (-1, 0), (0, -1)]
        for i, j, w, step in q:
            if step > k: continue
            if i == m - 1 and j == n - 1: return w
            for a, b in direct:
                i_new = i + a
                j_new = j + b
                if 0 <= i_new < m and 0<= j_new < n:
                    step_new = step + 1 if grid[i_new][j_new] == 1 else step
                    if (i_new, j_new, step_new) not in visited:
                        visited.add((i_new, j_new, step_new))
                        q.append((i_new, j_new, w + 1, step_new))
        return -1

迷宫

在迷宫中有一个球,里面有空的空间和墙壁。球可以通过滚上,下,左或右移动,
但它不会停止滚动直到撞到墙上。当球停止时,它可以选择下一个方向。
给定球的起始位置,目的地和迷宫,确定球是否可以停在终点。
迷宫由二维数组表示。1表示墙和0表示空的空间。你可以假设迷宫的边界都是墙。开始和目标坐标用行和列索引表示。

例:

输入:
map = 
[
 [0,0,1,0,0],
 [0,0,0,0,0],
 [0,0,0,1,0],
 [1,1,0,1,1],
 [0,0,0,0,0]
]
start = [0,4]
end = [3,2]
输出:
false
def bfs(nums, start, end):
    m = len(nums)
    n = len(nums[0])
    if not m or not n: return True
    visited = {(start[0], start[1])}
    q = [(start[0], start[1])]
    direction = [(1, 0), (0, 1), (-1, 0), (0, -1)]
    for x, y in q:
        # for a, b in qe:
        if x == end[0] and y == end[1]: return True
        for i, j in direction:
            a = x; b = y # 注意此处需要复制一份a、b,确保每一次while循环a、b的值都能为(x, y)!!!!
            while 0 <= a < m and 0 <= b < n and nums[a][b] == 0:
                a += i
                b += j
            a -= i
            b -= j
            if (a, b) not in visited:
                visited.add((a, b))
                q.append((a, b))
    return False
map = [[0,0,1,0,0],
 [0,0,0,0,0],
 [0,0,0,1,0],
 [1,1,0,1,1],
 [0,0,0,0,0]]
start = [0, 4]
end = [4, 4]
a = bfs(map, start, end)
print(a)

迷宫II

在迷宫中有一个球,里面有空的空间和墙壁。球可以通过滚上,下,左或右移动,但它不会停止滚动直到撞到墙上。当球停止时,它可以选择下一个方向。

给定球的起始位置,目标和迷宫,找到最短距离的球在终点停留。距离是由球从起始位置(被排除)到目的地(包括)所走过的空空间的数量来定义的。如果球不能停在目的地,返回-1。
迷宫由二维数组表示。1表示墙和0表示空的空间。你可以假设迷宫的边界都是墙。开始和目标坐标用行和列索引表示。

Example 1:
	Input:  
	(rowStart, colStart) = (0,4)
	(rowDest, colDest)= (4,4)
	0 0 1 0 0
	0 0 0 0 0
	0 0 0 1 0
	1 1 0 1 1
	0 0 0 0 0
 
	Output:  12
	
	Explanation:
	(0,4)->(0,3)->(1,3)->(1,2)->(1,1)->(1,0)->(2,0)->(2,1)->(2,2)->(3,2)->(4,2)->(4,3)->(4,4)
 
Example 2:
	Input:
	(rowStart, colStart) = (0,4)
	(rowDest, colDest)= (0,0)
	0 0 1 0 0
	0 0 0 0 0
	0 0 0 1 0
	1 1 0 1 1
	0 0 0 0 0
 
	Output:  6
	
	Explanation:
	(0,4)->(0,3)->(1,3)->(1,2)->(1,1)->(1,0)->(0,0)
	

这一题与上一题的代码基本一样,只是需要一个额外的变量记录所走的路程,因此将该变量加入到q列表中。代码如下:

def bfs(nums, start, end):
    m = len(nums)
    n = len(nums[0])
    if not m or not n: return True
    visited = {(start[0], start[1])}
    q = [(start[0], start[1], 0)]
    direction = [(1, 0), (0, 1), (-1, 0), (0, -1)]
    for x, y, dis in q:
        # for a, b in qe:
        if x == end[0] and y == end[1]: return dis
        for i, j in direction:
            a = x; b = y; d = dis# 注意此处需要复制一份a、b,确保每一次while循环的a、b并值都能为(x, y)!!!!
            while 0 <= a < m and 0 <= b < n and nums[a][b] == 0:
                a += i
                b += j
                d += 1
            a -= i
            b -= j
            d -= 1
            if (a, b) not in visited:
                visited.add((a, b))
                q.append((a, b, d))
    return -1
map = [[0,0,1,0,0],
 [0,0,0,0,0],
 [0,0,0,1,0],
 [1,1,0,1,1],
 [0,0,0,0,0]]
start = [0, 4]
end = [4, 4]
a = bfs(map, start, end)
print(a)

以上题均只是计算最后的结果,如何输出路径?在一些笔试中,常会对路径进行限制,(如一次华为的笔试,在连续的直线路径上,可以一下跳跃两步等等类似的情况)这时候就可以先求出路径,然后再对所选择的路径再进行处理(目前我想的是这样处理,有没有一种方法,在寻路径的过程就进行计算,本人还没有想到,希望后续能够学习到)

这里就先放别人的实现,参考链接。但是这种目前用在采用动态规划方法中比较好理解,但是如果题目是如上几种,采用的是BFS,该路径如何打印?本人也一直很无解-_-

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值