【面试】每日力扣 Day5 ---- 图论

200、岛屿数量

链接:200、岛屿数量

题目描述

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

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

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

在这里插入图片描述

方法 1

# 方法 1: 深度优先搜索(DFS)
from typing import List

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        # 如果网格为空,直接返回0
        if not grid:
            return 0

        # 定义深度优先搜索 (DFS) 函数
        def dfs(grid, i, j):
            # 检查当前坐标是否越界或是否已经是水('0')
            if i < 0 or i >= len(grid) or j < 0 or j >= len(grid[0]) or grid[i][j] == '0':
                return
            # 将当前坐标标记为已访问
            grid[i][j] = '0'
            # 递归调用DFS,分别向下、向上、向右和向左扩展搜索
            dfs(grid, i + 1, j)  # 向下
            dfs(grid, i - 1, j)  # 向上
            dfs(grid, i, j + 1)  # 向右
            dfs(grid, i, j - 1)  # 向左

        # 初始化岛屿计数器
        count = 0
        # 遍历网格的每个元素
        for i in range(len(grid)):  # 行
            for j in range(len(grid[0])):  # 列
                # 如果遇到陆地('1'),启动DFS
                if grid[i][j] == '1':
                    dfs(grid, i, j)
                    # 每次启动DFS时,岛屿计数器加1
                    count += 1

        # 返回最终的岛屿数量
        return count

具体分析

  1. 初始检查:如果网格为空,直接返回 0。
  2. DFS 函数:定义一个嵌套的 dfs 函数,用于递归地访问所有相连的陆地。
    • 检查当前坐标 (i, j) 是否越界或是否已经是水(‘0’)。如果是,则返回。
    • 将当前陆地标记为已访问(将其值改为 ‘0’)。
    • 递归调用 dfs 函数,分别向下、向上、向右和向左扩展搜索。
  3. 主循环:遍历网格的每个元素。
    • 如果遇到陆地(‘1’),则调用 dfs 函数开始搜索,并将岛屿计数器加 1。
  4. 返回结果:返回最终的岛屿数量。

示例

假设输入的网格如下:

[
  ['1', '1', '0', '0', '0'],
  ['1', '1', '0', '0', '0'],
  ['0', '0', '1', '0', '0'],
  ['0', '0', '0', '1', '1']
]

执行步骤如下:

  1. 初始化:
    • count = 0
  2. 遍历网格:
    • 遇到第一个 ‘1’(grid[0][0]),启动 DFS:
      • 标记 grid[0][0] 为 ‘0’
      • 标记 grid[0][1] 为 ‘0’
      • 标记 grid[1][0] 为 ‘0’
      • 标记 grid[1][1] 为 ‘0’
      • count = 1
    • 继续遍历,遇到第二个 ‘1’(grid[2][2]),启动 DFS:
      • 标记 grid[2][2] 为 ‘0’
      • count = 2
    • 继续遍历,遇到第三个 ‘1’(grid[3][3]),启动 DFS:
      • 标记 grid[3][3] 为 ‘0’
      • 标记 grid[3][4] 为 ‘0’
      • count = 3
  3. 返回结果:
    • 最终岛屿数量为 3。

这段代码通过 DFS 遍历所有相连的陆地,并将其标记为已访问,从而计算出岛屿的数量。

复杂度分析

  • 时间复杂度:(O(M × \times × N)),其中 (M) 是网格的行数,(N) 是网格的列数。原因是每个元素(无论是陆地还是水)都将被访问一次。
  • 空间复杂度:(O(M × \times × N)),在最坏情况下(整个网格都是陆地),DFS 递归栈的深度可能达到 (M × \times ×N)。此外,网格本身也占用了 (O(M × \times × N)) 的空间。

方法 2

# 方法 2: 广度优先搜索(BFS)
from collections import deque

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        # 如果网格为空,直接返回0
	    if not grid:
	        return 0
	
	    def bfs(grid, i, j):
	        queue = deque([(i, j)])
	        while queue:
	            x, y = queue.popleft()
	            # 检查当前坐标是否越界或是否已经是水('0')
	            if x < 0 or x >= len(grid) or y < 0 or y >= len(grid[0]) or grid[x][y] == '0':
	                continue
	            # 将当前坐标标记为已访问
	            grid[x][y] = '0'
	            # 将相邻的陆地坐标加入队列
	            queue.append((x + 1, y))  # 向下
	            queue.append((x - 1, y))  # 向上
	            queue.append((x, y + 1))  # 向右
	            queue.append((x, y - 1))  # 向左
	
	    # 初始化岛屿计数器
	    count = 0
	    # 遍历网格的每个元素
	    for i in range(len(grid)):
	        for j in range(len(grid[0])):
	            # 如果遇到陆地('1'),启动BFS
	            if grid[i][j] == '1':
	                bfs(grid, i, j)
	                # 每次启动BFS时,岛屿计数器加1
	                count += 1
	
	    # 返回最终的岛屿数量
	    return count

具体分析

  1. 初始检查:如果网格为空,直接返回 0。
  2. 定义 BFS 函数:使用队列来实现广度优先搜索。
    • 将起始坐标加入队列。
    • 当队列不为空时,取出队列中的坐标。
    • 检查当前坐标是否越界或是否已经是水(‘0’),如果是则跳过。
    • 将当前坐标标记为已访问(将其值设为’0’)。
    • 将相邻的陆地坐标(上下左右)加入队列。
  3. 初始化岛屿计数器:将岛屿计数器初始化为0。
  4. 遍历网格的每个元素:对于每个元素,如果遇到陆地(‘1’),启动BFS。每次启动BFS时,岛屿计数器加1。
  5. 返回结果:返回最终的岛屿数量。

示例

假设输入的网格如下:

[
  ['1', '1', '0', '0', '0'],
  ['1', '1', '0', '0', '0'],
  ['0', '0', '1', '0', '0'],
  ['0', '0', '0', '1', '1']
]

执行步骤如下:

  1. 初始化:
    • count = 0
  2. 遍历网格:
    • 遇到第一个 ‘1’(grid[0][0]),启动 BFS:
      • 标记 grid[0][0] 为 ‘0’
      • 标记 grid[0][1] 为 ‘0’
      • 标记 grid[1][0] 为 ‘0’
      • 标记 grid[1][1] 为 ‘0’
      • count = 1
    • 继续遍历,遇到第二个 ‘1’(grid[2][2]),启动 BFS:
      • 标记 grid[2][2] 为 ‘0’
      • count = 2
    • 继续遍历,遇到第三个 ‘1’(grid[3][3]),启动 BFS:
      • 标记 grid[3][3] 为 ‘0’
      • 标记 grid[3][4] 为 ‘0’
      • count = 3
  3. 返回结果:
    • 最终岛屿数量为 3。

这段代码通过 DFS 遍历所有相连的陆地,并将其标记为已访问,从而计算出岛屿的数量。

复杂度分析

  • 时间复杂度:O(M * N),其中 M 是网格的行数,N 是网格的列数。每个元素最多被访问一次。
  • 空间复杂度:O(min(M, N)),队列的最大长度取决于最坏情况下的岛屿大小。

深度优先搜索(DFS)VS. 广度优先搜索(BFS)

  1. 方法1:深度优先搜索(DFS)
    • 搜索策略:DFS 使用递归的方式,从一个起始点开始,沿着一个方向尽可能深入地搜索,直到不能再深入为止,然后回溯到上一个节点,继续搜索其他方向。
    • 实现方式:通过递归函数实现。
    • 优点:实现简单,代码简洁。
    • 缺点:在递归深度较大的情况下,可能会导致栈溢出。
  2. 方法2:广度优先搜索(BFS)
    • 搜索策略:BFS 使用队列,从一个起始点开始,首先访问其所有相邻节点,然后再逐层向外扩展,直到所有节点都被访问。
    • 实现方式:通过队列实现。
    • 优点:不会出现栈溢出的问题,适合处理大规模图。
    • 缺点:实现相对复杂,代码较长。
  3. 总结
    • DFS 适用于递归深度较小的情况,代码简洁,但可能会导致栈溢出。
    • BFS 适用于大规模图的搜索,避免了栈溢出的问题,但实现相对复杂。

方法 3

该算法使用并查集(Union-Find)来解决岛屿数量问题,通过路径压缩和合并操作,能够高效地处理大规模网格。与深度优先搜索(DFS)和广度优先搜索(BFS)相比,并查集在处理连通性问题时具有更好的性能和扩展性。

# 方法3 :并查集(Union-Find)
from typing import List

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        # 初始化并查集字典
        f = {}

        # 查找函数,带路径压缩
        def find(x):
            f.setdefault(x, x)  # 如果 x 不在字典中,则将其初始化为自身
            if f[x] != x:  # 如果 x 不是自身的父节点
                f[x] = find(f[x])  # 递归查找 x 的父节点,并进行路径压缩
            return f[x]  # 返回 x 的父节点

        # 合并函数,将两个节点合并到同一个集合中
        def union(x, y):
            f[find(x)] = find(y)  # 将 x 的父节点指向 y 的父节点

        # 如果网格为空,直接返回 0
        if not grid: return 0
        row = len(grid)  # 获取网格的行数
        col = len(grid[0])  # 获取网格的列数

        # 遍历网格的每个元素
        for i in range(row):
            for j in range(col):
                if grid[i][j] == "1":  # 如果遇到陆地('1')
                    # 检查上方和左方的相邻元素
                    for x, y in [[-1, 0], [0, -1]]:
                        tmp_i = i + x
                        tmp_j = j + y
                        # 如果相邻元素在网格范围内且为陆地,则合并
                        if 0 <= tmp_i < row and 0 <= tmp_j < col and grid[tmp_i][tmp_j] == "1":
                            union(tmp_i * row + tmp_j, i * row + j)

        # 使用集合存储所有不同的根节点
        res = set()
        for i in range(row):
            for j in range(col):
                if grid[i][j] == "1":  # 如果遇到陆地('1')
                    res.add(find((i * row + j)))  # 将其根节点加入集合
        return len(res)  # 返回集合的大小,即岛屿的数量

具体分析

  1. 初始化并查集:创建一个空字典 f 用于存储并查集。
  2. 定义查找函数 find:用于查找节点的根节点,并进行路径压缩。
  3. 定义合并函数 union:用于将两个节点合并到同一个集合中。
  4. 遍历网格:检查每个元素,如果是陆地(‘1’),则检查其上方和左方的相邻元素,如果相邻元素也是陆地,则将它们合并。
  5. 统计岛屿数量:使用集合存储所有不同的根节点,集合的大小即为岛屿的数量。

复杂度分析

  • 时间复杂度:O(M * N),其中 M 是网格的行数,N 是网格的列数。每个元素最多被访问一次,查找和合并操作的时间复杂度近似为 O(1)。
  • 空间复杂度:O(M * N),用于存储并查集的字典和集合。

130、被围绕的区域

链接:130、被围绕的区域

题目描述
在这里插入图片描述

方法 1

# 方法 1: DFS
class Solution:
    def solve(self, board: List[List[str]]) -> None:
        # 如果板子为空或第一行为空,直接返回
        if not board or not board[0]:
            return
        
        rows, cols = len(board), len(board[0])
        
        # 定义深度优先搜索 (DFS) 函数
        def dfs(i, j):
            # 检查当前坐标是否越界或是否已经不是 'O'
            if i < 0 or i >= rows or j < 0 or j >= cols or board[i][j] != 'O':
                return
            # 将当前坐标标记为特殊字符 '#'
            board[i][j] = '#'
            # 递归调用DFS,分别向下、向上、向右和向左扩展搜索
            dfs(i + 1, j)
            dfs(i - 1, j)
            dfs(i, j + 1)
            dfs(i, j - 1)
        
        # 遍历边界
        for i in range(rows):
            if board[i][0] == 'O':  # 左边界
                dfs(i, 0)
            if board[i][cols - 1] == 'O':  # 右边界
                dfs(i, cols - 1)
        
        for j in range(cols):
            if board[0][j] == 'O':  # 上边界
                dfs(0, j)
            if board[rows - 1][j] == 'O':  # 下边界
                dfs(rows - 1, j)
        
        # 遍历整个矩阵
        for i in range(rows):
            for j in range(cols):
                if board[i][j] == 'O':  # 将所有剩余的 'O' 替换为 'X'
                    board[i][j] = 'X'
                elif board[i][j] == '#':  # 将所有的 '#' 替换回 'O'
                    board[i][j] = 'O'

具体分析

  1. 初始化:
    • 检查输入的 board 是否为空或第一行是否为空,如果是,直接返回。
    • 获取 board 的行数 rows 和列数 cols
  2. 定义 DFS 函数:
    • dfs(i, j):从坐标 (i, j) 开始,递归地将所有连接的 ‘O’ 标记为特殊字符 #
    • 检查当前坐标是否越界或是否已经不是 ‘O’,如果是,直接返回。
    • 将当前坐标标记为 #
    • 递归调用 dfs,分别向下、向上、向右和向左扩展搜索。
  3. 遍历边界:
    • 遍历矩阵的四个边界(左边界、右边界、上边界、下边界),如果遇到 ‘O’,调用 dfs 将其及其连接的所有 ‘O’ 标记为 #
  4. 遍历整个矩阵:
    • 遍历整个矩阵,将所有剩余的 ‘O’ 替换为 ‘X’,将所有的 # 替换回 ‘O’。

复杂度分析

  • 时间复杂度:O(M * N),其中 M 是矩阵的行数,N 是矩阵的列数。我们需要遍历整个矩阵两次。
  • 空间复杂度:O(M * N),在最坏情况下,递归调用栈的深度可能达到矩阵的大小。

方法 2

# 方法 2: BFS
class Solution:
    def solve(self, board: List[List[str]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        # 如果板子为空或第一行为空,直接返回
        if not board or not board[0]:
            return
        
        # 获取行数和列数
        row = len(board)
        col = len(board[0])

        # 定义广度优先搜索 (BFS) 函数
        def bfs(i, j):
            from collections import deque
            # 创建一个双端队列
            queue = deque()
            # 将起始坐标加入队列
            queue.appendleft((i, j))
            while queue:
                # 从队列中取出当前坐标
                i, j = queue.pop()
                # 检查当前坐标是否在矩阵范围内且是否为 'O'
                if 0 <= i < row and 0 <= j < col and board[i][j] == "O":
                    # 将当前坐标标记为 'B'
                    board[i][j] = "B"
                    # 将相邻的四个方向的坐标加入队列
                    for x, y in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
                        queue.appendleft((i + x, j + y))

        # 遍历第一行和最后一行
        for j in range(col):
            # 第一行
            if board[0][j] == "O":
                bfs(0, j)
            # 最后一行
            if board[row - 1][j] == "O":
                bfs(row - 1, j)

        # 遍历第一列和最后一列
        for i in range(row):
            # 第一列
            if board[i][0] == "O":
                bfs(i, 0)
            # 最后一列
            if board[i][col - 1] == "O":
                bfs(i, col - 1)

        # 遍历整个矩阵
        for i in range(row):
            for j in range(col):
                # 将所有剩余的 'O' 替换为 'X'
                if board[i][j] == "O":
                    board[i][j] = "X"
                # 将所有的 'B' 替换回 'O'
                if board[i][j] == "B":
                    board[i][j] = "O"

详细解释

  1. 初始化:
    • 检查输入的 board 是否为空或第一行是否为空,如果是,直接返回。
    • 获取 board 的行数 row 和列数 col
  2. 定义 BFS 函数:
    • 使用双端队列 deque 来实现广度优先搜索。
    • 将起始坐标 (i, j) 加入队列。
    • 当队列不为空时,取出当前坐标 (i, j)
    • 检查当前坐标是否在矩阵范围内且是否为 ‘O’,如果是,将其标记为 ‘B’。
    • 将相邻的四个方向的坐标加入队列。
  3. 遍历边界:
    • 遍历第一行和最后一行,如果遇到 ‘O’,调用 BFS 将其及其连接的所有 ‘O’ 标记为 ‘B’。
    • 遍历第一列和最后一列,如果遇到 ‘O’,调用 BFS 将其及其连接的所有 ‘O’ 标记为 ‘B’。
  4. 遍历整个矩阵:
    • 遍历整个矩阵,将所有剩余的 ‘O’ 替换为 ‘X’,将所有的 ‘B’ 替换回 ‘O’。

复杂度分析

  • 时间复杂度:O(m * n)
  • 空间复杂度:O(m * n)

方法 3

# 方法 3: 并查集
from typing import List

class Solution:
    def solve(self, board: List[List[str]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        # 初始化并查集字典
        f = {}
        
        # 查找函数,带路径压缩
        def find(x):
            f.setdefault(x, x)
            if f[x] != x:
                f[x] = find(f[x])
            return f[x]
        
        # 合并函数
        def union(x, y):
            f[find(y)] = find(x)

        # 如果板子为空或第一行为空,直接返回
        if not board or not board[0]:
            return
        
        # 获取行数和列数
        row = len(board)
        col = len(board[0])
        
        # 虚拟节点,表示边界上的 'O'
        dummy = row * col
        
        # 遍历整个矩阵
        for i in range(row):
            for j in range(col):
                if board[i][j] == "O":
                    # 如果是边界上的 'O',与虚拟节点合并
                    if i == 0 or i == row - 1 or j == 0 or j == col - 1:
                        union(i * col + j, dummy)
                    else:
                        # 否则与相邻的 'O' 合并
                        for x, y in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
                            if board[i + x][j + y] == "O":
                                union(i * col + j, (i + x) * col + (j + y))
        
        # 遍历整个矩阵
        for i in range(row):
            for j in range(col):
                # 如果当前 'O' 与虚拟节点连通,保持不变
                if find(dummy) == find(i * col + j):
                    board[i][j] = "O"
                else:
                    # 否则替换为 'X'
                    board[i][j] = "X"

复杂度分析

  • 时间复杂度:O(m * n)
  • 空间复杂度:O(m * n)

133、克隆图

链接:133、克隆图

题目描述
在这里插入图片描述

方法 1

# 方法 1:DFS
class Solution:
    def cloneGraph(self, node: 'Node') -> 'Node':
        if not node:
            return None
        
        # 哈希表记录已访问的节点
        visited = {}
        
        # 定义递归函数克隆节点
        def dfs(node: 'Node') -> 'Node':
            if node in visited:
                return visited[node]
            
            # 创建新的克隆节点
            clone_node = Node(node.val)
            visited[node] = clone_node
            
            # 递归克隆所有邻居节点
            for neighbor in node.neighbors:
                clone_node.neighbors.append(dfs(neighbor))
            
            return clone_node
        
        # 从给定节点开始克隆图
        return dfs(node)

具体分析

  1. 定义解决方案类:
    • Solution 类包含 cloneGraph 方法,用于克隆图。
  2. 初始化哈希表:
    • 使用 visited 哈希表记录已访问的节点,并存储它们的克隆节点。
  3. 定义递归函数 dfs:
    • 如果节点已经被访问过,直接返回克隆节点。
    • 否则,创建一个新的克隆节点,并将其存储在 visited 哈希表中。
    • 递归克隆所有邻居节点,并将克隆的邻居节点添加到克隆节点的邻居列表中。
  4. 从给定节点开始克隆图:
    • 调用 dfs 函数,从给定节点开始克隆图。

复杂度分析

  • 时间复杂度:O(N + E),其中 N 是节点数,E 是边数。每个节点和每条边都会被访问一次。
  • 空间复杂度:O(N),用于存储克隆节点的哈希表和递归调用栈。

方法 2

# 方法 2:BFS
from collections import deque

class Solution:
    def cloneGraph(self, node: 'Node') -> 'Node':
        if not node:
            return None
        
        # 哈希表记录已访问的节点
        visited = {}
        
        # 初始化队列进行 BFS
        queue = deque([node])
        
        # 克隆起始节点并存入哈希表
        visited[node] = Node(node.val)
        
        # 开始 BFS 遍历
        while queue:
            # 取出队列中的第一个节点
            n = queue.popleft()
            
            # 遍历当前节点的所有邻居
            for neighbor in n.neighbors:
                if neighbor not in visited:
                    # 如果邻居节点未被访问过,则克隆并加入队列
                    visited[neighbor] = Node(neighbor.val)
                    queue.append(neighbor)
                # 将克隆的邻居节点加入当前克隆节点的邻居列表
                visited[n].neighbors.append(visited[neighbor])
        
        # 返回克隆的起始节点
        return visited[node]

具体分析

  1. 初始化哈希表:
    • 使用 visited 哈希表记录已访问的节点,并存储它们的克隆节点。
  2. 初始化队列进行 BFS:
    • 使用 deque 初始化队列,并将起始节点加入队列。
  3. 克隆起始节点并存入哈希表:
    • 克隆起始节点,并将其存入 visited 哈希表。
  4. 开始 BFS 遍历:
    • 使用 while 循环遍历队列中的节点。
    • 取出队列中的第一个节点 n。
    • 遍历当前节点 n 的所有邻居。
    • 如果邻居节点未被访问过,则克隆邻居节点,并将其加入队列。
    • 将克隆的邻居节点加入当前克隆节点的邻居列表。
  5. 返回克隆的起始节点:
    • 返回 visited 哈希表中存储的克隆起始节点。

复杂度分析

  • 时间复杂度:O(N + E),其中 N 是节点数,E 是边数。每个节点和每条边都会被访问一次。
  • 空间复杂度:O(N),用于存储克隆节点的哈希表和递归调用栈。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值