leetcode&lintcode分类刷题:图论(三、多源最小距离问题)

2023-09-18 11:49:30

1、本次总结的题目通常是多个源头节点分别求解到达目标节点的最小距离,目标节点可能为多个,也可能为一个;要采用广度优先搜索的方法,但先提前入队的不是源头节点了,而是目标节点,由目标节点为基准一圈一圈的更新能够达到的“新目标”位置,每一圈更新能够达到的位置最多只会访问一次
2、常见的题型会设置障碍物,在一些细节的特殊情况上,需要稍微注意下:源头节点&目标节点都不存在或其中一个不存在的情况,距离标记采用同尺寸的距离矩阵
3、有的题目比较难一点,会求解多个源头节点到所有目标节点距离之和的最小值

542. 01 矩阵

多源最小距离问题的基础题目,需要注意细节的特殊情况:没有目标节点或者没有源头节点

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

1162. 地图分析

多源最小距离问题的基础题目,需要注意细节的特殊情况:没有目标节点或者没有源头节点

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

994. 腐烂的橘子

多源最小距离问题的基础题目,需要注意细节的特殊情况:没有目标节点或者没有源头节点;甚至目标和源头节点都没有,只有障碍物(此时与没有源头节点情况一致)

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

663 · 墙和门

多源最小距离问题的基础题目,这道题不需要注意特殊情况,也不需要设置标记矩阵,因为题目要求在原矩阵上进行修改

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

803 · 建筑物之间的最短距离

这个题目不同的地方在于 要求输入源到所有目标位置的距离总和,即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

803 · 建筑物之间的最短距离

这道题目和之前的都不太一样,任意位置都能达到,不严格区分源头和目标节点了,因此直接求行和列排序后的中位数即为最近的见面位置,再把距离相加即可

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
更多推荐

软件定义汽车时代,1 亿行代码的安全保障,极狐GitLab 这么做!

本文整理自极狐GitLab解决方案部总监张扬老师在AUTOSEMO会议上的主题分享“驾驭代码激增浪潮,护航软件定义汽车”。软件定义汽车的挑战“软件吞噬世界”,这是网景创始人MarcAndreessen在2011年说的一句话。这些年软件行业的飞速发展也验证了这句话。智能手机就是一个很鲜活的例子,各种app彻底改变了人们的

健身完全手册

文章目录饮食完全手册摄入总量日内分配来源和配餐方法专题&错误训练完全手册训练分化动作模式胸背手肩腿臀腹训练计划减脂完全手册胸肌训练(原理+动作+计划+饮食)健身训练的分化、动作、配重体态大师饮食完全手册参考视频:💪🏻B站版《健身新手的饮食完全手册》™健身饮食不是管理具体食物,而是管理食物背后的碳蛋脂摄入总量定碳蛋脂

抖音seo短视频矩阵系统源代码开发部署分享

一、抖音seo短视频矩阵系统源码开发需要用到以下技术:前端技术:HTML、CSS、JavaScript等。后端技术:PHP、MySQL等。视频处理技术:FFmpeg等。抓取技术:爬虫技术,如Python的Requests、BeautifulSoup等。推荐算法:协同过滤算法、基于内容的推荐算法等。SEO优化:关键词分析

蓝桥杯每日一题2023.9.21

蓝桥杯2021年第十二届省赛真题-异或数列-C语言网(dotcpp.com)题目描述Alice和Bob正在玩一个异或数列的游戏。初始时,Alice和Bob分别有一个整数a和b,有一个给定的长度为n的公共数列X1,X2,···,Xn。Alice和Bob轮流操作,Alice先手,每步可以在以下两种选项中选一种:选项1:从数

0 杂项知识

文章目录加密算法对称加密非对称加密散列加密sha-512与md5算法的对比加密算法参照:链接一般将加密算法分为三种:对称加密,非对称加密,散列加密对称加密对称加密就是只存在一把钥匙,这把钥匙可以用来加密文件,也可以用来解密文件(个人理解)常用于需要经常性沟通的加密,即给出钥匙的对方与己方需要有经常性的沟通。非对称加密非

【FAQ】安防监控系统/视频云存储EasyCVR平台安全检查Proxy出现sql injection的漏洞,该如何修改?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快,可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等,以及支持厂家私有协议与SDK接入,包括海康Ehome、海大宇等设备的SDK等。最近有用户反馈,在使用视频监控系统EasyCVR平台安全扫描时,发现

企业工程项目管理系统源码(三控:进度组织、质量安全、预算资金成本、二平台:招采、设计管理)

工程项目管理软件(工程项目管理系统)对建设工程项目管理组织建设、项目策划决策、规划设计、施工建设到竣工交付、总结评估、运维运营,全过程、全方位的对项目进行综合管理工程项目各模块及其功能点清单一、系统管理1、数据字典:实现对数据字典标签的增删改查操作2、编码管理:实现对系统编码的增删改查操作3、用户管理:管理和查看用户角

JVM调优笔记

双亲委派机制app---->ext----->bootstrap保证系统的核心库不被修改沙箱安全机制限制系统资源访问,将java代码限制在虚拟机特定的运行范围中基本组件字节码校验器确保java类文件遵循java规范,帮助java程序实现内存保护类加载器nativeJava的作用范围达不到了,需要调用底层栈栈内存主管程序

Java流式编程的使用

流式编程的使用步骤使用流式编程的步骤就是:设置数据源,设置数据处理的方式,设置收集结果的方式。使用filter方法实现过滤条件例子为下(查询年龄大于18的用户):@TestpublicvoidstreamTest1(){List<Student>students=Arrays.asList(newStudent("to

搜索——最短路模型,多源bfs

最短路模型,即求从起点到终点的最短路径,我们可以选择dijkstra,spfa等等,在这里我们可以利用宽搜(bfs)的特性来求,因为bfs是一层一层的向外扩展的,所以当我们第一次遍历到终点时,所在的层数即为起点到终点的最短路径。多源bfs,顾名思义,多个起点的bfs,与一般的bfs不同的地方在于根据题目要求,将多个起点

国外访问学者面签需要注意什么?

国外访问学者面签是前往国外进行学术研究或合作的关键一步,因此需要谨慎准备。以下是知识人网小编整理的一些需要注意的重要事项,以确保面签顺利进行:1.签证申请材料准备:首先,要仔细阅读所申请国家的签证要求,并准备所需的申请材料。通常,这些材料包括护照、签证申请表、邀请函、学术证明、财务证明和研究计划等。2.邀请函:如果你是

热文推荐