小 S 的黑白块迷宫题解 | 豆包MarsCode AI 刷题

88 阅读8分钟
一、小 S 的黑白块迷宫
1. 题目介绍

小S需要在一个 n×m 网格迷宫中,从起点 (1,1) 到终点 (n,m),经过尽可能少的黑色格子。网格中的每个格子可以是:

  • 白色格子(值为 0,通过成本为 0)。
  • 黑色格子(值为 1,通过成本为 1)。
2、思路

这类问题可以抽象为加权最短路径问题,权值为 01。使用 0-1 BFS(双端队列 BFS)可以高效解决。

实现步骤:
  • 初始化

    • 定义一个二维数组 dist 记录从起点到每个格子的最小黑色格子数量,初始化为 inf
    • 使用双端队列 dq,将起点加入队列。
  • BFS 遍历

    • 每次从队列中取出一个格子 (x, y)

    • 遍历四个相邻方向 (nx, ny),计算到达新位置的代价:

      • 如果是白色格子(grid[nx][ny] == 0),优先处理,加入队列前端。
      • 如果是黑色格子(grid[nx][ny] == 1),加入队列后端。
  • 返回结果

    • 终点的 dist[n-1][m-1] 即为最少经过的黑色格子数量。
3、代码实现
from collections import deque

def min_black_cells(n, m, grid):
    directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
    dist = [[float('inf')] * m for _ in range(n)]
    dist[0][0] = grid[0][0]
    dq = deque([(0, 0)])
    
    while dq:
        x, y = dq.popleft()
        
        for dx, dy in directions:
            nx, ny = x + dx, y + dy
            
            if 0 <= nx < n and 0 <= ny < m:
                new_cost = dist[x][y] + grid[nx][ny]
                
                if new_cost < dist[nx][ny]:
                    dist[nx][ny] = new_cost
                    if grid[nx][ny] == 0:
                        dq.appendleft((nx, ny))
                    else:
                        dq.append((nx, ny))
    
    return dist[n-1][m-1]

二、类似题目

一个在植物园中的探险问题,小明需要在不回头的情况下尽可能参观完植物园,同时要尽量避免踩到障碍物。题目给出了植物园的长度、前进的最小和最大距离、以及障碍物的位置,要求计算小明在抵达终点时最少需要踩到的障碍物数量。

输入格式:

  1. 第一行:一个正整数 L,表示植物园的长度。
  2. 第二行:三个正整数 ST 和 M,分别表示前进的最小距离、最大距离及园内障碍物的个数。这三个数之间用一个空格隔开,M 后不准有空格。
  3. 第三行M 个不同的正整数,表示这些障碍物在路径上的位置。所有相邻的整数之间用一个空格隔开,最后一个正整数之后不可有空格。

输入样例1:

植物园长度 L:10

前进的最小距离 S:2

前进的最大距离 T3

障碍物个数 M:5

障碍物位置:2 3 5 6 7

输出:2

BFS 解题思路:

  1. 初始化:从起点 0 开始,将其加入队列,并记录当前位置的障碍物数量。
  2. BFS 遍历:每次从队列中取出一个位置,尝试从该位置前进 S 到 T 之间的所有可能步数,并将新位置加入队列。
  3. 障碍物处理:如果新位置是障碍物,则增加障碍物数量。
  4. 终止条件:当到达或超过终点 L 时,记录当前的障碍物数量,并继续搜索直到队列为空。
from collections import deque

def min_obstacles_bfs(L, S, T, M, obstacles):
    # 将障碍物位置放入集合中,方便快速查找
    obstacle_set = set(obstacles)
    
    # 初始化队列,每个元素为 (当前位置, 当前踩到的障碍物数量)
    queue = deque([(0, 0)])
    
    # 记录已经访问过的位置,避免重复访问
    visited = set([0])
    
    # 记录最少踩到的障碍物数量
    min_obstacles = float('inf')
    
    while queue:
        current_pos, current_obstacles = queue.popleft()
        
        # 如果到达或超过终点,更新最少障碍物数量
        if current_pos >= L:
            min_obstacles = min(min_obstacles, current_obstacles)
            continue
        
        # 尝试从当前位置前进 S 到 T 之间的所有步数
        for step in range(S, T + 1):
            next_pos = current_pos + step
            
            # 如果新位置未访问过,则加入队列
            if next_pos not in visited:
                visited.add(next_pos)
                next_obstacles = current_obstacles + (1 if next_pos in obstacle_set else 0)
                queue.append((next_pos, next_obstacles))
    
    return min_obstacles

# 输入样例
L = 10
S = 2
T = 3
M = 5
obstacles = [1, 3, 5, 7, 9]

# 输出结果
print(min_obstacles_bfs(L, S, T, M, obstacles))

三、知识总结

1. 核心算法
  • BFS

    • 适用于路径规划问题,依次扩展当前层的所有可能状态。
    • 时间复杂度为 O(V + E),其中 V 为节点数,E 为边数。
  • 0-1 BFS

    • 优化处理权值为 01 的图,利用双端队列实现优先处理低权值的路径。
    • 时间复杂度与普通 BFS 相同,但减少了冗余计算。
2. 状态定义与转移方程
  • 网格问题

    • 状态定义:dist[i][j] 表示从起点到格子 (i, j) 所需经过的最少黑色格子数。
    • 转移方程:dist[nx][ny] = min(dist[nx][ny], dist[x][y] + grid[nx][ny])
  • 植物园问题

    • 状态定义:current_obstacles 表示到达当前位置所需踩到的最少障碍物数量。
    • 状态转移:从 current_pos 跳跃到 next_posnext_obstacles = current_obstacles + (1 if next_pos in obstacle_set else 0)
3. 时间与空间复杂度
  • 网格问题

    • 时间复杂度:O(n * m),遍历网格的所有节点和边。
    • 空间复杂度:O(n * m),用于存储距离数组。
  • 植物园问题

    • 时间复杂度:O(L * (T - S)),每个位置最多扩展 (T - S) 个状态。
    • 空间复杂度:O(L),用于存储队列和已访问节点集合。
4. 应用场景
  • 网格问题

    • 路径规划:机器人或游戏中的路径优化。
    • 图像处理:计算从起点到目标点的最优像素路径。
  • 植物园问题

    • 跳跃游戏:模拟固定步长范围的跳跃路径。
    • 路线设计:从起点到终点的最优路线选择。

五、学习心得

通过学习上述 BFS(广度优先搜索)和 0-1 BFS 的范式,深刻感受到其在解决路径规划、图搜索以及动态规划问题中的强大应用能力。BFS 的核心在于逐层扩展状态,并在每个状态中检查所有可能的下一步。这种方式确保了在无权图中,第一个到达目标节点的路径一定是最短路径。以下是我对 BFS 范式的理解与收获。

BFS 的核心思想与应用

BFS 通过队列实现,借助“先进先出”的特性逐层扩展搜索空间。其最大的优势在于系统性和最优性:通过逐层扩展,它能够确保搜索到的路径是最短的。应用 BFS 时,关键在于:

  1. 队列管理:将初始状态(起点)加入队列,并逐步扩展其所有合法邻居。
  2. 访问标记:使用集合或数组记录已访问的节点,避免重复搜索。
  3. 终止条件:当目标状态被首次访问时,即可返回最优解。

这种范式广泛应用于无权图的最短路径问题、网络流量分析、迷宫求解等领域。在具体实现中,数据结构的选用(如 deque 提供的双端队列)显著提升了算法的效率和可读性。

0-1 BFS 的优势与特性

在带权图中,0-1 BFS 利用双端队列高效处理权值为 01 的边。其核心在于将权值为 0 的状态加入队列前端优先处理,权值为 1 的状态加入队列后端延后处理。这种优先级的设计,保证了路径权重累加的最优性,同时避免了传统 Dijkstra 算法的复杂性和冗余计算。

在具体实现中,学习到了以下优化技巧:

  1. 动态优先队列:通过双端队列模拟权重优先级,不需要显式排序。
  2. 边的处理顺序:动态插入权值较小的状态,使得 BFS 在本质上与权值排序无缝结合。
  3. 高效性:相比 Dijkstra,0-1 BFS 的时间复杂度为 O(V + E),非常适合处理稀疏图或路径权值单一的问题。
应用场景的多样性

上述范式的学习让我更加体会到 BFS 和 0-1 BFS 的适用性:

  • 路径规划:机器人移动、迷宫问题、游戏场景路径优化等问题的最优解。
  • 动态规划优化:将动态规划的状态转移映射为图的边权,实现更灵活的状态转移。
  • 网络分析:从社交网络的传播模型到通信网络的最短路径计算,都有 BFS 的身影。
总结与感悟

BFS 和 0-1 BFS 的学习不仅帮助我在算法设计上有了更清晰的思路,还让我感受到优雅的算法可以通过巧妙的结构设计大幅降低复杂度。特别是 0-1 BFS 的优先级设计,启发了我在解决实际问题时更多地关注问题的结构特性,而不是机械套用模板。同时,这种范式让我更加体会到数学与计算机科学结合的美感。通过不断的练习和总结,我相信可以将 BFS 和 0-1 BFS 应用于更复杂的问题场景中,实现高效优雅的解决方案。