走迷宫 II

题目描述

迷宫地宫地形信息记录于一维字符串数组 maze 中,maze[i] 为仅由 "."、"X"、"S" 和 "E" 组成的字符串,其中:

  • "X" 表示墙,不可直接通行,但可选择将墙推倒后通过;
  • "." 表示空地,可以通行;
  • "S" 表示迷宫入口;
  • "E" 表示迷宫出口。

请你求出入口到出口所需的最少推墙次数。

注意: 迷宫中仅有一个入口和一个出口。

示例 1:

输入:
maze = ["XSXX","....","XXX.","X.E."]

输出: 0

解释:
不需要推倒任何墙就可以直接从入口到达出口,下图是一种可行的方案:

示例 2:

输入:
maze = ["XSXX","X...","XX..","EX.."]

输出: 1

解释:
最少需要推倒 1 次墙,下图是一种可行的方案:

提示:

  • 1 <= maze.length, maze[i].length <= 100

问题分析

这是一个带权重的最短路径问题,具有以下特点:

  • 移动规则:
    • 移动到空地(".")或出口("E"):代价为 0
    • 推倒墙("X")并移动:代价为 1
  • 目标:找到从入口("S")到出口("E")的最小推墙次数
  • 算法选择:
    • 由于只有两种权重(0和1),最适合使用 0-1 BFS
    • 也可以使用 Dijkstra 算法

解题思路

0-1 BFS 方法

核心思想:

  • 使用双端队列(Deque)
  • 代价为0的移动加入队列头部
  • 代价为1的移动加入队列尾部
  • 这样保证每次取出的都是当前代价最小的状态

算法步骤:

  1. 找到起点和终点位置
  2. 使用双端队列进行BFS
  3. 对于每个位置,尝试向四个方向移动
  4. 根据目标位置的类型决定代价和入队方式

解法实现

Java 实现

import java.util.ArrayDeque;
import java.util.Deque;

/**
 * 3. 走迷宫 II
 * 0-1 BFS
 */
class Solution {

    // 四个方向:上下左右
    private static final int[][] DIRS = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};

    public int solve(String[] maze) {
        int rows = maze.length;
        int cols = maze[0].length();

        // 找到起点和终点
        int startRow = -1, startCol = -1;
        int endRow = -1, endCol = -1;

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (maze[i].charAt(j) == 'S') {
                    startRow = i;
                    startCol = j;
                }
                if (maze[i].charAt(j) == 'E') {
                    endRow = i;
                    endCol = j;
                }
            }
        }

        return bfs(maze, startRow, startCol, endRow, endCol);
    }

    private int bfs(String[] maze, int startRow, int startCol, int endRow, int endCol) {
        int rows = maze.length;
        int cols = maze[0].length();

        // 距离数组,记录到达每个位置的最少推墙次数
        int[][] dist = new int[rows][cols];
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                dist[i][j] = Integer.MAX_VALUE;
            }
        }

        // 双端队列用于0-1 BFS
        Deque<int[]> deque = new ArrayDeque<>();
        deque.offer(new int[]{startRow, startCol});
        dist[startRow][startCol] = 0;

        while (!deque.isEmpty()) {
            int[] cur = deque.poll();
            int curRow = cur[0];
            int curCol = cur[1];

            // 如果到达终点,返回结果
            if (curRow == endRow && curCol == endCol) {
                return dist[curRow][curCol];
            }

            // 尝试四个方向
            for (int[] dir : DIRS) {
                int newRow = curRow + dir[0];
                int newCol = curCol + dir[1];

                // 检查边界
                if (newRow < 0 || newRow >= rows || newCol < 0 || newCol >= cols) {
                    continue;
                }

                char cell = maze[newRow].charAt(newCol);
                int newCost;

                // 计算移动代价
                if (cell == '.' || cell == 'E') {
                    newCost = dist[curRow][curCol]; // 移动到空格或终点,代价不变
                } else if (cell == 'X') {
                    newCost = dist[curRow][curCol] + 1; // 移动到墙,代价加1
                } else {
                    continue; // 遇到其他字符,跳过
                }

                // 如果找到更优路径,更新距离并加入队列
                if (newCost < dist[newRow][newCol]) {
                    dist[newRow][newCol] = newCost;

                    // 0-1 BFS的关键:根据代价决定加入队列的位置
                    if (cell == '.' || cell == 'E') {
                        deque.offerFirst(new int[]{newRow, newCol}); // 代价为0,加入队列头部
                    } else {
                        deque.offerLast(new int[]{newRow, newCol}); // 代价为1,加入队列尾部
                    }
                }
            }
        }

        // 无法到达终点
        return -1;
    }
}

C# 实现

/**
 * 3. 走迷宫 II
 * 0-1 BFS
 */
public class Solution
{
    // 四个方向:上下左右
    private int[,] _dirs = new int[,] { { 0, 1 }, { 0, -1 }, { 1, 0 }, { -1, 0 } };

    public int Solve(string[] maze)
    {
        int rows = maze.Length;
        int cols = maze[0].Length;

        // 找到起点和终点
        int startRow = -1, startCol = -1;
        int endRow = -1, endCol = -1;
        for (int i = 0; i < rows; i++)
        {
            for (int j = 0; j < cols; j++)
            {
                if (maze[i][j] == 'S')
                {
                    startRow = i;
                    startCol = j;
                }
                else if (maze[i][j] == 'E')
                {
                    endRow = i;
                    endCol = j;
                }
            }
        }

        return Bfs(maze, startRow, startCol, endRow, endCol);
    }

    private int Bfs(string[] maze, int startRow, int startCol, int endRow, int endCol)
    {
        int rows = maze.Length;
        int cols = maze[0].Length;

        // 距离数组,记录到达每个位置的最少推墙次数
        int[,] dist = new int[rows, cols];
        for (int i = 0; i < rows; i++)
        {
            for (int j = 0; j < cols; j++)
            {
                dist[i, j] = int.MaxValue;
            }
        }

        // 双端队列用于0-1 BFS
        LinkedList<(int, int)> deque = new LinkedList<(int, int)>();
        deque.AddLast((startRow, startCol));
        dist[startRow, startCol] = 0;

        while (deque.Count > 0)
        {
            (int, int) cur = deque.First.Value;
            deque.RemoveFirst();
            
            int curRow = cur.Item1;
            int curCol = cur.Item2;

            // 如果到达终点,返回结果
            if (curRow == endRow && curCol == endCol)
            {
                return dist[curRow, curCol];
            }

            // 尝试四个方向
            for (int i = 0; i < 4; i++)
            {
                int newRow = curRow + _dirs[i, 0];
                int newCol = curCol + _dirs[i, 1];
                // 检查边界
                if (newRow < 0 || newRow >= rows || newCol < 0 || newCol >= cols)
                {
                    continue;
                }

                char cell = maze[newRow][newCol];
                // 计算移动代价
                int newCost;
                if (cell == '.' || cell == 'E')
                {
                    newCost = dist[curRow, curCol]; // 移动到空格或终点,代价为当前代价
                }
                else if (cell == 'X')
                {
                    newCost = dist[curRow, curCol] + 1; // 移动到墙,代价为当前代价加1
                }
                else
                {
                    continue; // 忽略其他字符
                }

                // 如果找到更优路径,更新距离并加入队列
                if (newCost < dist[newRow, newCol])
                {
                    dist[newRow, newCol] = newCost;

                    // 0-1 BFS的关键:根据代价决定加入队列的位置
                    if (cell == '.' || cell == 'E')
                    {
                        deque.AddFirst((newRow, newCol)); // 移动到空格或终点,加入队列头部
                    }
                    else
                    {
                        deque.AddLast((newRow, newCol)); // 移动到墙,加入队列尾部
                    }
                }
            }
        }

        return -1;
    }
}

算法复杂度分析

时间复杂度: O(M × N)

  • M 和 N 分别是迷宫的行数和列数
  • 每个位置最多被访问一次

空间复杂度: O(M × N)

  • 需要 dist 数组存储到达每个位置的最小代价
  • 双端队列在最坏情况下可能存储所有位置
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值