1、本次总结的题目通常是多个源头节点分别求解到达目标节点的最小距离,目标节点可能为多个,也可能为一个;要采用广度优先搜索的方法,但先提前入队的不是源头节点了,而是目标节点,由目标节点为基准一圈一圈的更新能够达到的“新目标”位置,每一圈更新能够达到的位置最多只会访问一次
2、常见的题型会设置障碍物,在一些细节的特殊情况上,需要稍微注意下:源头节点&目标节点都不存在或其中一个不存在的情况,距离标记采用同尺寸的距离矩阵
3、有的题目比较难一点,会求解多个源头节点到所有目标节点距离之和的最小值
多源最小距离问题的基础题目,需要注意细节的特殊情况:没有目标节点或者没有源头节点
from typing import List, Optional, Union
from collections import deque
'''
542. 01 矩阵 (974 · 01 矩阵 lintcote)
给定一个由 0 和 1 组成的矩阵 mat,请输出一个大小相同的矩阵,其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。
两个相邻元素间的距离为 1 。
示例 1:
输入:mat = [[0,0,0],[0,1,0],[0,0,0]]
输出:[[0,0,0],[0,1,0],[0,0,0]]
题眼:
思路:多输入源到多目标位置的最短距离:以 目标位置 为中心进行BFS(从输入位置开始的话,会重复计算相邻的源到目标位置的距离,会超时)
注意特殊情况:没有目标节点或者没有源头节点
'''
class Solution:
def updateMatrix(self, mat: List[List[int]]) -> List[List[int]]:
m, n = len(mat), len(mat[0])
result = [[0]*n for _ in range(m)]
visited = [[False] * n for _ in range(m)] # 既是重复访问的标志,也是距离更新的标志
que = deque()
# 第一次遍历:将所有目标位置0添加到队列中
for i in range(m):
for j in range(n):
if mat[i][j] == 0:
que.append((i, j))
visited[i][j] = True
# 特殊情况1、没有目标节点,题目说了至少有一个0
# 特殊情况2、没有源头节点,直接返回result
if len(que) == m * n:
return result
# 第二次遍历,更新得到 距离为+1 的目标位置“0”
while que:
x, y = que.popleft()
for tx, ty in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
if 0 <= tx < m and 0 <= ty < n and not visited[tx][ty]: # 有效、且没有重复访问
result[tx][ty] = result[x][y] + 1 # 距离更新公式
que.append((tx, ty))
visited[tx][ty] = True
return result
if __name__ == "__main__":
obj = Solution()
while True:
try:
in_line = input().strip().split('=')[1].strip()[1: -1]
mat = []
for row in in_line.split(']')[: -1]:
mat.append([int(n) for n in row.split('[')[1].split(',')])
print(obj.updateMatrix(mat))
except EOFError:
break
多源最小距离问题的基础题目,需要注意细节的特殊情况:没有目标节点或者没有源头节点
from typing import List, Optional, Union
from collections import deque
'''
1162. 地图分析 (1911 · 地图分析 lintcote)
你现在手里有一份大小为n x n的 网格 grid,上面的每个 单元格 都用0和1标记好了。其中0代表海洋,1代表陆地。
请你找出一个海洋单元格,这个海洋单元格到离它最近的陆地单元格的距离是最大的,并返回该距离。如果网格上只有陆地或者海洋,请返回-1。
我们这里说的距离是「曼哈顿距离」(Manhattan Distance):(x0, y0) 和(x1, y1)这两个单元格之间的距离是|x0 - x1| + |y0 - y1|。
示例 1:
输入:grid = [[1,0,1],[0,0,0],[1,0,1]]
输出:2
解释:
海洋单元格 (1, 1) 和所有陆地单元格之间的距离都达到最大,最大距离为 2。
题眼:
思路:多输入源到多目标位置的最短距离:以 目标位置 为中心进行BFS(从输入位置开始的话,会重复计算相邻的源到目标位置的距离,会超时);目标位置不断长大!
注意特殊情况:没有目标节点或者没有源头节点
'''
class Solution:
def maxDistance(self, grid: List[List[int]]) -> int:
n = len(grid)
distance = [[0]*n for _ in range(n)] # 标记每个位置到目标位置1的距离
visited = [[False] * n for _ in range(n)] # 既是重复访问的标志,也是距离更新的标志
que = deque()
maxDist = 0 # 最远距离标志:只有陆地或海洋时 该值不会改变
# 第一次遍历:将所有目标位置1添加到队列中
for i in range(n):
for j in range(n):
if grid[i][j] == 1:
que.append((i, j))
visited[i][j] = True
# 特殊情况1、没有目标节点陆地
# 特殊情况2、没有源头节点海洋
if len(que) == 0 or len(que) == n * n:
return -1
# 第二次遍历,更新得到 距离为+1 的目标位置“1”
while que:
x, y = que.popleft()
for tx, ty in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
if 0 <= tx < n and 0 <= ty < n and not visited[tx][ty]: # 有效、且没有重复访问
distance[tx][ty] = distance[x][y] + 1 # 距离更新公式
maxDist = distance[tx][ty] # 保持持续更新,跟踪最大的距离值:最后一个值就是最大的,可以不取max
que.append((tx, ty))
visited[tx][ty] = True
return maxDist
if __name__ == "__main__":
obj = Solution()
while True:
try:
in_line = input().strip().split('=')[1].strip()[1: -1]
mat = []
for row in in_line.split(']')[: -1]:
mat.append([int(n) for n in row.split('[')[1].split(',')])
print(obj.maxDistance(mat))
except EOFError:
break
多源最小距离问题的基础题目,需要注意细节的特殊情况:没有目标节点或者没有源头节点;甚至目标和源头节点都没有,只有障碍物(此时与没有源头节点情况一致)
from typing import List, Optional, Union
from collections import deque
'''
994. 腐烂的橘子
在给定的m x n网格grid中,每个单元格可以有以下三个值之一:
值0代表空单元格;值1代表新鲜橘子;值2代表腐烂的橘子。
每分钟,腐烂的橘子周围 4 个方向上相邻 的新鲜橘子都会腐烂。
返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回-1
示例 1:
输入:grid = [[2,1,1],[1,1,0],[0,1,1]]
输出:4
题眼:题目其实是求 输入源1 距离 目标位置2 的最大距离,如果有无法到达的1,返回-1
思路:多输入源到多目标位置的最短距离(有障碍物):以 目标位置 为中心进行BFS;目标位置不断长大!
注意特殊情况:没有目标节点或者没有源头节点;甚至目标和源头节点都没有,只有障碍物(此时与没有源头节点情况一致)
'''
class Solution:
def orangesRotting(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
minute = [[0] * n for _ in range(m)] # 定义一个 “时间距离” 矩阵:每个元素表示1、2(忽略0)与最近的2的距离
visited = [[False] * n for _ in range(m)] # 既是重复访问的标志,也是距离更新的标志
dq = deque()
count1 = 0 # 新鲜橘子的数量:既可以标志是否存在1,又可以计数判断新鲜橘子是否被腐烂完
maxMinute = 0 # 所有1中距离2的最远时间:没有更新说明2不存在(前提是count1>0),是不可能的情况
# 第一次遍历、将所有坏橘子(值为2的位置)添加到队列中
for i in range(m):
for j in range(n):
if grid[i][j] == 2:
dq.append((i, j))
visited[i][j] = True
elif grid[i][j] == 1:
count1 += 1
# 特殊情况1:起始时刻没有新鲜橘子(0分钟)
if count1 == 0:
return 0
# 特殊情况2:起始时刻没有坏橘子(不可能使得元格中没有新鲜橘子)——已经确定有新鲜橘子了
if len(dq) == 0:
return -1
# 第二次遍历、坏橘子+1扩展 得到新的坏橘子“2”
while dq:
x, y = dq.popleft()
for tx, ty in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
if 0 <= tx < m and 0 <= ty < n and grid[tx][ty] == 1 and not visited[tx][ty]:
dq.append((tx, ty))
visited[tx][ty] = True
minute[tx][ty] = minute[x][y] + 1
maxMinute = minute[tx][ty] # 保持持续更新,跟踪最大的距离值
count1 -= 1
# 检查是否还存在新鲜橘子
if count1 > 0:
return -1 # 存在,返回-1不可能使得元格中没有新鲜橘子
return maxMinute # 不存在,返回距离2最大的1对应的距离
if __name__ == "__main__":
obj = Solution()
while True:
try:
in_line = input().strip().split('=')[1].strip()[1: -1]
mat = []
for row in in_line.split(']')[: -1]:
mat.append([int(n) for n in row.split('[')[1].split(',')])
print(obj.orangesRotting(mat))
except EOFError:
break
多源最小距离问题的基础题目,这道题不需要注意特殊情况,也不需要设置标记矩阵,因为题目要求在原矩阵上进行修改
from typing import List, Optional, Union
from collections import deque
'''
lintcode
663 · 墙和门
您将获得一个使用这三个可能值初始化的 m×n 2D 网格。
-1 - 墙壁或障碍物。0 - 门。INF - Infinity是一个空房间。我们使用值 2 ^ 31 - 1 = 2147483647 来表示INF,您可以假设到门的距离小于 2147483647。
在代表每个空房间的网格中填入到距离最近门的距离。如果不可能到达门口,则应填入 INF。
示例 1:
输入:grid = [[2147483647,-1,0,2147483647],[2147483647,2147483647,2147483647,-1],[2147483647,-1,2147483647,-1],[0,-1,2147483647,2147483647]]
输出:[[3,-1,0,1],[2,2,1,-1],[1,-1,2,-1],[0,-1,3,4]]
题眼:题目其实是求 输入源-房间INF 距离 目标位置-门0 的最近距离,如果无法到达,则为INF保持不变
思路:多输入源到多目标位置的最短距离(有障碍物):以 目标位置 为中心进行BFS;目标位置不断长大!
'''
class Solution:
def walls_and_gates(self, rooms: List[List[int]]):
m, n = len(rooms), len(rooms[0])
dq = deque()
# 因为是在原数组上修改,不必判断不满足条件的情况
# 第一次遍历:将所有 门-0 加入到 队列
for i in range(m):
for j in range(n):
if rooms[i][j] == 0:
dq.append((i, j))
# 第二次遍历:目标位置-门0 +1 扩展得到新的 ”门-0“
while dq:
x, y = dq.popleft()
for tx, ty in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
if 0 <= tx < m and 0 <= ty < n and rooms[tx][ty] == 2147483647:
dq.append((tx, ty))
rooms[tx][ty] = rooms[x][y] + 1 # 在原数组上进行距离更新,相当于是有 标记 过重复了
if __name__ == "__main__":
obj = Solution()
while True:
try:
in_line = input().strip().split('=')[1].strip()[1: -1]
mat = []
for row in in_line.split(']')[: -1]:
mat.append([int(n) for n in row.split('[')[1].split(',')])
obj.walls_and_gates(mat)
print(mat)
except EOFError:
break
这个题目不同的地方在于 要求输入源到所有目标位置的距离总和,即distance矩阵保存的是 距离序列,是比较难的一道题目
from typing import List, Optional, Union
from collections import deque
'''
lintcode
803 · 建筑物之间的最短距离
你想在一个空旷的土地上盖房子,在最短的距离内到达所有的建筑物。你只能上下左右移动。你得到的是一个二维数组网格的值为0、1或2,其中:
每一个0标记一个空的土地,你可以自由地通过。
每一个1标记一个你不能通过的建筑物。
每一个2标记一个你不能通过的障碍。
至少会有一个建筑物。如果根据上述规则不可能建造这样的房子,则返回 -1。
示例 1:
输入:grid = [[1,0,2,0,1],[0,0,0,0,0],[0,0,1,0,0]]
输出:7
解释:点(1,2)是建造房屋理想的空地,因为3+3+1=7的总行程距离最小。所以返回7。
题眼:
思路:单输入源到多目标位置的最短距离(有障碍物):以 目标位置 为中心进行BFS;目标位置不断长大!
这个题目不同的地方在于 要求输入源到所有目标位置的距离总和,即distance矩阵保存的是 距离序列。
'''
class Solution:
def shortest_distance(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
countBuild = 0
distance = {} # 用字典维护距离信息:每一个输入源0到建筑物的 距离序列
for i in range(m):
for j in range(n):
if grid[i][j] == 1: # 对每一个建筑物进行 每一个输入源0的 BFS遍历
countBuild += 1 # 统计建筑物数量
# BFS过程
visited = [[False] * n for _ in range(m)] # 还是要有visited矩阵标记重复访问
dq = deque()
dq.append((i, j))
visited[i][j] = True
step = 0
while dq:
step += 1
size = len(dq)
for _ in range(size): # 搞了半天,发现还是一圈一圈遍历的方法最适合
x, y = dq.popleft()
for tx, ty in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
if 0 <= tx < m and 0 <= ty < n and grid[tx][ty] == 0 and not visited[tx][ty]: # 有效、为0、第一次
dq.append((tx, ty))
visited[tx][ty] = True
if (tx, ty) not in distance:
distance[(tx, ty)] = [step]
else:
distance[(tx, ty)].append(step)
# 判断distance中每个输入源0到达目标位置1的距离和
result = -1
for key in distance:
if len(distance[key]) == countBuild: # 必须能到达所有目标位置
if result == -1:
result = sum(distance[key])
else:
result = min(result, sum(distance[key]))
return result
if __name__ == "__main__":
obj = Solution()
while True:
try:
in_line = input().strip().split('=')[1].strip()[1: -1]
mat = []
for row in in_line.split(']')[: -1]:
mat.append([int(n) for n in row.split('[')[1].split(',')])
print(obj.shortest_distance(mat))
except EOFError:
break
这道题目和之前的都不太一样,任意位置都能达到,不严格区分源头和目标节点了,因此直接求行和列排序后的中位数即为最近的见面位置,再把距离相加即可
from typing import List, Optional, Union
from collections import deque
'''
lintcode
912 · 最佳见面地点
有一群住在不同地方的朋友(两个或以上)想要在某地见面,要求他们去往目的地的路程和最短。现在给一个0、1组成的二维数组,1表示此地有一个人居住。
使用曼哈顿距离作为计算总距离,公式为:(p1, p2) = |p2.x - p1.x| + |p2.y - p1.y|
示例 1:
输入: grid = [[1,0,0,0,1],[0,0,0,0,0],[0,0,1,0,0]]
输出: 6
解释: 点(0, 2)是最佳见面地点,最小的路程总和为2+2+2=6,返回6。
思路:这道题目和之前的都不太一样,任意位置都能达到,不严格区分源头和目标节点了,因此直接求行和列排序后的中位数即为最近的见面位置,再把距离相加即可
'''
class Solution:
# 曼哈顿距离,行列的最佳点分开求,组合起来就是最佳点
# 二维->一维。求中位数
def min_total_distance(self, grid: List[List[int]]) -> int:
rows, cols = [], []
m, n = len(grid), len(grid[0])
for i in range(m):
for j in range(n):
if grid[i][j] == 1:
rows.append(i)
cols.append(j)
rows.sort()
cols.sort()
mid = len(rows) // 2
v1, v2 = rows[mid], cols[mid]
res = 0
for i in range(m):
for j in range(n):
if grid[i][j] == 1:
res += abs(v1 - i) + abs(v2 - j)
return res
if __name__ == "__main__":
obj = Solution()
while True:
try:
in_line = input().strip().split('=')[1].strip()[1: -1]
mat = []
for row in in_line.split(']')[: -1]:
mat.append([int(n) for n in row.split('[')[1].split(',')])
print(obj.min_total_distance(mat))
except EOFError:
break