青训营X豆包MarsCode 学习笔记:计算异世界中的安全区 | 豆包MarsCode AI刷题

94 阅读8分钟

一、题目背景与分析

题目描述: 小F被神秘力量带入了一个魔幻世界,这里危机四伏。为了在异世界中生存,小F需要找到安全区。异世界可以被表示为一个大小为 ( n \times m ) 的二维数组,每个格子的值代表该位置的危险程度。小F的能力值为 ( X ),当某个格子的危险程度小于等于 ( X ) 时,这个格子是安全的。如果多个安全的格子相邻(上下左右连通),它们可以构成一个安全区。你需要帮助小F计算出一共有多少个安全区。

示例

  • 对于 ( n = 3 ),( m = 3 ),( X = 4 ),二维数组为 [[2, 3, 3], [3, 3, 3], [3, 3, 3]],只有一个安全区。
  • 对于 ( n = 2 ),( m = 2 ),( X = 5 ),二维数组为 [[6, 6], [6, 4]],只有一个安全区。
  • 对于 ( n = 3 ),( m = 3 ),( X = 3 ),二维数组为 [[1, 2, 2], [2, 3, 3], [3, 4, 5]],只有一个安全区。

二、思路分析

  1. 问题建模

    • 问题可以转化为在二维数组中找到所有连通的安全区域。
    • 安全区定义为所有危险程度小于等于 ( X ) 且相邻的格子组成的区域。
  2. 算法选择

    • 使用深度优先搜索(DFS)来遍历每个格子,标记已经访问过的格子,确保每个安全区只被统计一次。
    • 通过递归的方式,从一个安全格子出发,访问其所有相邻的安全格子,直到没有新的安全格子可以访问为止。
  3. 具体步骤

    • 初始化一个与输入数组大小相同的访问标记数组 visited,用于记录每个格子是否已被访问。
    • 定义一个DFS函数,用于递归访问相邻的安全格子。
    • 遍历整个二维数组,对于每个未访问过的安全格子,调用DFS函数,每次调用DFS函数后,增加安全区计数。
    • 最终返回安全区的数量。

三、代码实现

def solution(n: int, m: int, X: int, a: list[list[int]]) -> int:
    # 初始化访问标记数组
    visited = [[False] * m for _ in range(n)]
    
    # 定义DFS函数
    def dfs(i, j):
        # 如果越界或者已经访问过,或者不安全,返回
        if i < 0 or i >= n or j < 0 or j >= m or visited[i][j] or a[i][j] > X:
            return
        # 标记当前格子为已访问
        visited[i][j] = True
        # 递归访问相邻的格子
        dfs(i + 1, j)
        dfs(i - 1, j)
        dfs(i, j + 1)
        dfs(i, j - 1)
    
    # 初始化安全区计数
    safe_zones = 0
    
    # 遍历所有格子
    for i in range(n):
        for j in range(m):
            # 如果当前格子是安全的且未访问过
            if a[i][j] <= X and not visited[i][j]:
                # 调用DFS函数
                dfs(i, j)
                # 增加安全区计数
                safe_zones += 1
    
    # 返回安全区的数量
    return safe_zones

if __name__ == '__main__':
    print(solution(3, 3, 4, [[2, 3, 3], [3, 3, 3], [3, 3, 3]]) == 1)
    print(solution(2, 2, 5, [[6, 6], [6, 4]]) == 1)
    print(solution(3, 3, 3, [[1, 2, 2], [2, 3, 3], [3, 4, 5]]) == 1)

四、知识点总结

  1. 深度优先搜索(DFS)

    • DFS是一种用于遍历或搜索树或图的算法。通过递归或栈来实现。
    • 在本题中,DFS用于从一个安全格子出发,访问所有相邻的安全格子,直到没有新的安全格子可以访问为止。
    • 关键点在于递归的终止条件和访问标记的使用。
  2. 二维数组的遍历

    • 遍历二维数组时,通常使用两层嵌套循环,外层循环遍历行,内层循环遍历列。
    • 在本题中,遍历二维数组是为了找到所有未访问过的安全格子,调用DFS函数进行深度搜索。
  3. 访问标记数组

    • 访问标记数组用于记录每个格子是否已被访问,防止重复访问。
    • 在本题中,访问标记数组 visited 与输入数组 a 大小相同,初始值为 False,表示所有格子均未被访问。
  4. 连通区域的检测

    • 连通区域是指在二维数组中,所有相邻且满足特定条件的格子组成的区域。
    • 在本题中,连通区域是指所有相邻且危险程度小于等于 ( X ) 的格子组成的区域。

五、算法优化

优化深度优先搜索(DFS)算法的效率可以从多个角度入手,包括减少不必要的递归调用、使用合适的数据结构、剪枝等。以下是一些具体的优化方法:

1. 剪枝

剪枝是通过提前终止无效的搜索路径来减少不必要的递归调用,从而提高算法的效率。

示例:连通区域检测

在检测连通区域时,如果某个格子已经被访问过,或者不符合条件(如危险程度大于 ( X )),可以直接跳过,避免不必要的递归调用。

def dfs(i, j):
    if i < 0 or i >= n or j < 0 or j >= m or visited[i][j] or a[i][j] > X:
        return
    visited[i][j] = True
    dfs(i + 1, j)
    dfs(i - 1, j)
    dfs(i, j + 1)
    dfs(i, j - 1)

2. 使用合适的数据结构

选择合适的数据结构可以显著提高算法的效率。例如,使用集合(set)来存储已访问的节点,可以快速查找和更新状态。

示例:使用集合存储已访问节点

def solution(n: int, m: int, X: int, a: list[list[int]]) -> int:
    visited = set()
    
    def dfs(i, j):
        if i < 0 or i >= n or j < 0 or j >= m or (i, j) in visited or a[i][j] > X:
            return
        visited.add((i, j))
        dfs(i + 1, j)
        dfs(i - 1, j)
        dfs(i, j + 1)
        dfs(i, j - 1)
    
    safe_zones = 0
    for i in range(n):
        for j in range(m):
            if a[i][j] <= X and (i, j) not in visited:
                dfs(i, j)
                safe_zones += 1
    return safe_zones

3. 避免重复计算

在某些情况下,可以预先计算一些中间结果,避免在递归过程中重复计算。

示例:预处理危险程度

如果危险程度的计算比较复杂,可以预先计算并存储结果,避免在递归过程中重复计算。

4. 限制递归深度

递归深度过深可能会导致栈溢出,可以通过设置递归深度限制来避免这种情况。Python 中可以使用 sys.setrecursionlimit 来设置递归深度。

示例:设置递归深度

import sys
sys.setrecursionlimit(10000)

def solution(n: int, m: int, X: int, a: list[list[int]]) -> int:
    visited = [[False] * m for _ in range(n)]
    
    def dfs(i, j):
        if i < 0 or i >= n or j < 0 or j >= m or visited[i][j] or a[i][j] > X:
            return
        visited[i][j] = True
        dfs(i + 1, j)
        dfs(i - 1, j)
        dfs(i, j + 1)
        dfs(i, j - 1)
    
    safe_zones = 0
    for i in range(n):
        for j in range(m):
            if a[i][j] <= X and not visited[i][j]:
                dfs(i, j)
                safe_zones += 1
    return safe_zones

5. 使用迭代代替递归

在某些情况下,使用迭代代替递归可以避免栈溢出问题,并且通常更高效。

示例:使用栈实现DFS

def solution(n: int, m: int, X: int, a: list[list[int]]) -> int:
    visited = [[False] * m for _ in range(n)]
    
    def dfs(i, j):
        stack = [(i, j)]
        while stack:
            x, y = stack.pop()
            if x < 0 or x >= n or y < 0 or y >= m or visited[x][y] or a[x][y] > X:
                continue
            visited[x][y] = True
            stack.append((x + 1, y))
            stack.append((x - 1, y))
            stack.append((x, y + 1))
            stack.append((x, y - 1))
    
    safe_zones = 0
    for i in range(n):
        for j in range(m):
            if a[i][j] <= X and not visited[i][j]:
                dfs(i, j)
                safe_zones += 1
    return safe_zones

6. 并行处理

对于大规模数据,可以考虑使用并行处理技术,如多线程或多进程,来加速计算。

示例:使用多线程

import threading

def dfs(i, j, visited, a, n, m, X):
    stack = [(i, j)]
    while stack:
        x, y = stack.pop()
        if x < 0 or x >= n or y < 0 or y >= m or visited[x][y] or a[x][y] > X:
            continue
        visited[x][y] = True
        stack.append((x + 1, y))
        stack.append((x - 1, y))
        stack.append((x, y + 1))
        stack.append((x, y - 1))

def solution(n: int, m: int, X: int, a: list[list[int]]) -> int:
    visited = [[False] * m for _ in range(n)]
    threads = []
    safe_zones = 0
    
    for i in range(n):
        for j in range(m):
            if a[i][j] <= X and not visited[i][j]:
                thread = threading.Thread(target=dfs, args=(i, j, visited, a, n, m, X))
                thread.start()
                threads.append(thread)
                safe_zones += 1
    
    for thread in threads:
        thread.join()
    
    return safe_zones

优化DFS算法的效率可以从多个角度入手,包括剪枝、使用合适的数据结构、避免重复计算、限制递归深度、使用迭代代替递归以及并行处理。通过这些方法,可以显著提高算法的性能,使其在处理大规模数据时更加高效。理解这些优化方法,不仅有助于解决当前的问题,还可以在其他类似的搜索和遍历问题中发挥作用。 通过这次学习,我不仅加深了对深度优先搜索(DFS)这一算法的理解,还掌握了如何将抽象的算法应用于实际问题中。这种从理论到实践的学习过程,使我更加自信地面对各种编程挑战。在未来的学习和工作中,我将继续探索更多有趣的问题,不断提升自己的编程能力和解决问题的能力。

六、学习心得

  1. 理解问题的本质

    • 本题的核心在于找到所有连通的安全区域。通过将问题转化为图的连通区域检测,可以清晰地理解问题的本质。
    • 深度优先搜索(DFS)是解决此类问题的有效方法,通过递归的方式,可以高效地遍历所有连通的格子。
  2. 代码实现的细节

    • 在实现过程中,需要注意边界条件的处理,如越界检查、访问标记的更新等。
    • 通过逐步调试和测试,可以确保代码的正确性和鲁棒性。
    • 访问标记数组的使用是关键,它可以有效地防止重复访问,提高算法的效率。
  3. 实际应用的拓展

    • 本题的解法不仅适用于连通区域的检测,还可以应用于其他需要遍历和搜索的场景,如迷宫问题、岛屿问题等。
    • 掌握DFS的基本操作和应用,对于解决类似问题具有重要的意义。