Leetcode刷题笔记61:图论1(深度搜索理论基础-797.所有可能的路径-200. 岛屿数量-695. 岛屿的最大面积)

241 阅读7分钟

导语

leetcode刷题笔记记录,主要记录题目包括:

知识点

在计算机科学和离散数学中,图是网络结构的一种抽象表示,用来表示对象之间的一些对应关系。图由一组节点(或称为顶点)和一组连接这些节点的边(或称为弧)组成。

分类

  1. 有向图(Directed Graph):边有方向。
  2. 无向图(Undirected Graph):边没有方向。
  3. 加权图(Weighted Graph):每条边都有一个权重。
  4. 无权图(Unweighted Graph):每条边权重相同。
  5. 有环图(Cyclic Graph):至少存在一个节点,可以从该节点出发沿若干条边回到该节点。
  6. 无环图(Acyclic Graph):没有任何环。

深度优先搜索(DFS)

定义

深度优先搜索(Depth-First Search,DFS)是一种用于遍历或搜索树或图的算法。这个算法会尽可能深地搜索树的分支。

应用示例

  1. 迷宫问题:找出从起点到终点的路径。
  2. 有向无环图(DAG)中的拓扑排序。
  3. 寻找图中的连通组件。
  4. 最短路径和最长路径问题。
  5. 网络爬虫中的网页遍历。

DFS 和回溯算法

回溯算法

回溯算法是一种通过探索所有可能的候选解来找出所有解的算法。如果候选解被确认不是一个解的话,就会"回溯"以取消最后的计算步骤。

关系

  1. DFS 是一种特殊类型的回溯算法。在图和树的遍历中,如果走不通了,就会回溯到上一个节点,继续尝试其他分支。
  2. 回溯算法通常用于找到所有(或一部分)解,而深度优先搜索通常用于找到某一解。
  3. 在实现时,DFS 和回溯算法都使用递归来模拟全部可能的解。

通过深度优先搜索和回溯算法,可以解决许多复杂的计算问题,特别是与路径、排序和排列有关的问题。这两个算法在很多领域有广泛的应用,比如人工智能、机器学习、网络设计等。

Leetcode 797. 所有可能的路径

题目描述

给你一个有 n 个节点的 有向无环图(DAG) ,请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序

 graph[i] 是一个从节点 i 可以访问的所有节点的列表(即从节点 i 到节点 graph[i][j]存在一条有向边)。

 

示例 1:

输入: graph = [[1,2],[3],[3],[]]
输出: [[0,1,3],[0,2,3]]
解释: 有两条路径 0 -> 1 -> 30 -> 2 -> 3

示例 2:

输入: graph = [[4,3,1],[3,2,4],[3],[4],[]]
输出: [[0,4],[0,3,4],[0,1,3,4],[0,1,2,3,4],[0,1,4]]

 

提示:

  • n == graph.length
  • 2 <= n <= 15
  • 0 <= graph[i][j] < n
  • graph[i][j] != i(即不存在自环)
  • graph[i] 中的所有元素 互不相同
  • 保证输入为 有向无环图(DAG)

解法

使用深搜三部曲:

  1. 确认递归函数、参数:
# 定义一个深度优先搜索(DFS)方法
    def dfs(self, graph: List[List[int]], root: int) -> None:

这里将设置两个全局变量:

  • path:用于存储单次搜索的路径
  • result:用于存储所有符合条件的路径
  1. 确认终止条件
        # 如果当前的节点是 n-1(即图的终点)
        if root == len(graph) - 1:
            # 将当前路径添加到结果列表中
            self.result.append(self.path[:])
            return
  1. 处理目前搜索节点出发的路径
        # 遍历从当前节点出发可达的所有节点
        for i in graph[root]:
            # 将下一个节点添加到当前路径中
            self.path.append(i)
            # 从下一个节点开始继续进行深度优先搜索
            self.dfs(graph, i)
            # 回溯:将刚才添加的节点从当前路径中移除
            self.path.pop()

完整代码如下:

from typing import List

class Solution:
    def __init__(self):
        self.result = []  # 用于存储所有从 0 到 n-1 的路径
        self.path = [0]  # 用于存储当前正在探索的路径

    # 定义一个深度优先搜索(DFS)方法
    def dfs(self, graph: List[List[int]], root: int) -> None:
        # 如果当前的节点是 n-1(即图的终点)
        if root == len(graph) - 1:
            # 将当前路径添加到结果列表中
            self.result.append(self.path[:])
            return
        # 遍历从当前节点出发可达的所有节点
        for i in graph[root]:
            # 将下一个节点添加到当前路径中
            self.path.append(i)
            # 从下一个节点开始继续进行深度优先搜索
            self.dfs(graph, i)
            # 回溯:将刚才添加的节点从当前路径中移除
            self.path.pop()

    # 主函数
    def allPathsSourceTarget(self, graph: List[List[int]]) -> List[List[int]]:
        # 从节点 0(起点)开始进行深度优先搜索
        self.dfs(graph, 0)
        # 返回所有从节点 0 到节点 n-1 的路径
        return self.result

Leetcode 200. 岛屿数量

题目描述

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

 

示例 1:

输入: grid = [  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出: 1

示例 2:

输入: grid = [  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
输出: 3

 

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 300
  • grid[i][j] 的值为 '0' 或 '1'

解法

这个问题是一个经典的图遍历问题,可以通过深度优先搜索(DFS)来解决。

这里的基本思路是:遍历整个二维网格,当我们遇到一个“1”(陆地)时,我们从这个点开始进行深度优先搜索,将与这个点相连的所有陆地(即整个岛屿)都标记为已访问(例如将“1”改为“0”或其他标记),以避免重复计算。同时,岛屿计数加1。

以下是使用DFS实现的Python代码:

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        # 初始化岛屿数量为 0
        count = 0
        # 获取网格的行数和列数
        m, n = len(grid), len(grid[0])
        
        # 定义深度优先搜索函数
        def dfs(i, j):
            # 判断边界条件以及是否为陆地('1')
            if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] != '1':
                return
            
            # 将访问过的陆地标记为 '2',以避免重复访问
            grid[i][j] = '2'
            
            # 对相邻的四个方向进行深度优先搜索
            dfs(i - 1, j)  # 向上
            dfs(i + 1, j)  # 向下
            dfs(i, j - 1)  # 向左
            dfs(i, j + 1)  # 向右
        
        # 遍历整个网格
        for i in range(m):
            for j in range(n):
                # 当遇到一个未访问的陆地('1')时,进行深度优先搜索,并增加岛屿数量
                if grid[i][j] == '1':
                    dfs(i, j)
                    count += 1
        
        # 返回最终的岛屿数量
        return count

Leetcode 695. 岛屿的最大面积

题目描述

给你一个大小为 m x n 的二进制矩阵 grid 。

岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。

岛屿的面积是岛上值为 1 的单元格的数目。

计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。

 

示例 1:

输入: grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]]
输出: 6
解释: 答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1

示例 2:

输入: grid = [[0,0,0,0,0,0,0,0]]
输出: 0

 

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 50
  • grid[i][j] 为 0 或 1

解法

使用dfs,将比那里的位置标记为2,之后只要递归访问四个方向,并将它们的面积相加,加 1 表示当前陆地的面积

                return 1 + dfs(i - 1, j) + dfs(i + 1, j) + dfs(i, j - 1) + dfs(i, j + 1)

在具体调用时,定义一个最大面积,每次将返回的岛屿面积和最大面积比较,记录最新的最大面积。完整版代码如下:

from typing import List

class Solution:
    def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
        max_area = 0  # 初始化最大岛屿面积为 0
        m, n = len(grid), len(grid[0])  # 获取网格的行数和列数

        # 定义 DFS 函数
        def dfs(i, j):
            if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] != 1:  # 边界检查和陆地检查
                return 0  # 返回 0,表示不增加面积
            else:
                grid[i][j] = 2  # 将已经访问的陆地标记为 2
                
                # 递归访问四个方向,并将它们的面积相加,加 1 表示当前陆地的面积
                return 1 + dfs(i - 1, j) + dfs(i + 1, j) + dfs(i, j - 1) + dfs(i, j + 1)
        
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 1:  # 当找到一个未访问的陆地时
                    max_area = max(max_area, dfs(i, j))  # 更新最大岛屿面积

        return max_area  # 返回最大岛屿面积