LeetCode 1263:推箱子

发布于:2025-07-28 ⋅ 阅读:(17) ⋅ 点赞:(0)

LeetCode 1263:推箱子

在这里插入图片描述

问题本质:推箱子的最小步骤

需将箱子(B)推到目标位置(T),玩家(S)可自由移动(避开墙和箱子),但推箱子必须站在旁边并向箱子方向推。目标是找到最小推动次数,若无法实现则返回 -1

核心思路:双状态 BFS + 辅助可达性检查

1. 状态定义

每个状态由 箱子位置 (bx, by) 和玩家位置 (px, py) 组成,推动次数为当前步骤。通过 BFS 遍历状态,保证首次到达目标时的推动次数最小。

2. 关键观察
  • 玩家移动时,箱子位置固定,需避开箱子(视为障碍)。
  • 推箱子时,玩家必须站在箱子的反方向(如推箱子向上,玩家需站在箱子下方),推动后玩家移至箱子原位置。
3. 算法流程
  1. 定位初始状态:找到玩家、箱子、目标的坐标。
  2. 主 BFS:遍历所有可能的推箱子动作,记录状态和推动次数。
  3. 辅助 BFS:判断玩家在箱子固定时,能否从当前位置移动到推箱子所需的位置(反方向)。

算法步骤详解

步骤 1:定位初始位置

遍历网格,标记玩家(S)、箱子(B)、目标(T)的坐标:

int sx = -1, sy = -1, bx = -1, by = -1, tx = -1, ty = -1;
for (int i = 0; i < grid.length; i++) {
    for (int j = 0; j < grid[0].length; j++) {
        if (grid[i][j] == 'S') { sx = i; sy = j; }
        else if (grid[i][j] == 'B') { bx = i; by = j; }
        else if (grid[i][j] == 'T') { tx = i; ty = j; }
    }
}
步骤 2:主 BFS 初始化

队列存储状态 [bx, by, px, py, moves],访问集合避免重复处理:

Queue<int[]> queue = new LinkedList<>();
queue.offer(new int[]{bx, by, sx, sy, 0});
Set<String> visited = new HashSet<>();
visited.add(bx + "," + by + "," + sx + "," + sy);
步骤 3:推箱子的方向遍历

尝试四个方向(上、下、左、右)推动箱子,核心逻辑:

  1. 计算新状态:箱子新位置 (newBx, newBy),玩家推前位置 (pushPx, pushPy)(箱子反方向)。
  2. 合法性检查:新箱子位置需在网格内且非墙,推前位置需合法(非墙、在网格内)。
  3. 可达性检查:调用辅助 BFS,判断玩家能否从当前位置 (currPx, currPy) 移动到推前位置 (pushPx, pushPy)(避开当前箱子位置)。
  4. 状态更新:若合法且未访问,加入队列,推动次数 +1
步骤 4:辅助 BFS(玩家可达性)

判断玩家在箱子位置 (blockX, blockY) 固定时,能否从 (startX, startY) 到达 (endX, endY)

private boolean canReach(char[][] grid, int startX, int startY, int endX, int endY, int blockX, int blockY) {
    int m = grid.length, n = grid[0].length;
    boolean[][] visited = new boolean[m][n];
    Queue<int[]> q = new LinkedList<>();
    q.offer(new int[]{startX, startY});
    visited[startX][startY] = true;
    int[][] dirs = {{-1,0},{1,0},{0,-1},{0,1}};
    
    while (!q.isEmpty()) {
        int[] curr = q.poll();
        int x = curr[0], y = curr[1];
        if (x == endX && y == endY) return true;
        for (int[] d : dirs) {
            int nx = x + d[0], ny = y + d[1];
            if (nx >= 0 && nx < m && ny >= 0 && ny < n 
                && !visited[nx][ny] 
                && grid[nx][ny] != '#' 
                && !(nx == blockX && ny == blockY)) { // 避开箱子
                visited[nx][ny] = true;
                q.offer(new int[]{nx, ny});
            }
        }
    }
    return false;
}
步骤 5:终止条件

若箱子到达目标位置,返回当前推动次数;队列空则返回 -1

if (currBx == tx && currBy == ty) return moves;

完整代码(Java)

import java.util.*;

class Solution {
    public int minPushBox(char[][] grid) {
        int m = grid.length, n = grid[0].length;
        int sx = -1, sy = -1, bx = -1, by = -1, tx = -1, ty = -1;
        
        // 定位初始位置:S、B、T
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 'S') { sx = i; sy = j; }
                else if (grid[i][j] == 'B') { bx = i; by = j; }
                else if (grid[i][j] == 'T') { tx = i; ty = j; }
            }
        }
        
        // BFS队列:[bx, by, px, py, moves]
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[]{bx, by, sx, sy, 0});
        Set<String> visited = new HashSet<>();
        visited.add(bx + "," + by + "," + sx + "," + sy);
        
        // 推动方向:上、下、左、右
        int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        
        while (!queue.isEmpty()) {
            int[] curr = queue.poll();
            int currBx = curr[0], currBy = curr[1];
            int currPx = curr[2], currPy = curr[3];
            int moves = curr[4];
            
            // 检查箱子是否到达目标
            if (currBx == tx && currBy == ty) {
                return moves;
            }
            
            // 尝试四个方向推动箱子
            for (int[] dir : dirs) {
                int dx = dir[0], dy = dir[1];
                int newBx = currBx + dx, newBy = currBy + dy; // 箱子新位置
                int pushPx = currBx - dx, pushPy = currBy - dy; // 玩家推前位置(反方向)
                
                // 检查新箱子位置合法性:网格内、非墙
                if (newBx < 0 || newBx >= m || newBy < 0 || newBy >= n) continue;
                if (grid[newBx][newBy] == '#') continue;
                
                // 检查推前位置合法性:网格内、非墙
                if (pushPx < 0 || pushPx >= m || pushPy < 0 || pushPy >= n) continue;
                if (grid[pushPx][pushPy] == '#') continue;
                
                // 检查玩家能否从当前位置到达推前位置(避开当前箱子)
                if (!canReach(grid, currPx, currPy, pushPx, pushPy, currBx, currBy)) {
                    continue;
                }
                
                // 推动后,玩家位置是箱子原位置
                int newPx = currBx, newPy = currBy;
                String key = newBx + "," + newBy + "," + newPx + "," + newPy;
                
                // 新状态未访问则加入队列
                if (!visited.contains(key)) {
                    visited.add(key);
                    queue.offer(new int[]{newBx, newBy, newPx, newPy, moves + 1});
                }
            }
        }
        
        // 无法到达目标
        return -1;
    }
    
    // 辅助函数:判断玩家在箱子(blockX, blockY)处时,能否从(startX, startY)到(endX, endY)
    private boolean canReach(char[][] grid, int startX, int startY, int endX, int endY, int blockX, int blockY) {
        int m = grid.length, n = grid[0].length;
        boolean[][] visited = new boolean[m][n];
        Queue<int[]> q = new LinkedList<>();
        q.offer(new int[]{startX, startY});
        visited[startX][startY] = true;
        int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        
        while (!q.isEmpty()) {
            int[] curr = q.poll();
            int x = curr[0], y = curr[1];
            if (x == endX && y == endY) return true;
            for (int[] d : dirs) {
                int nx = x + d[0], ny = y + d[1];
                if (nx >= 0 && nx < m && ny >= 0 && ny < n 
                    && !visited[nx][ny] 
                    && grid[nx][ny] != '#' 
                    && !(nx == blockX && ny == blockY)) {
                    visited[nx][ny] = true;
                    q.offer(new int[]{nx, ny});
                }
            }
        }
        return false;
    }
}

复杂度与优化

  • 时间复杂度
    主 BFS 状态数为 O((m×n)²)(箱子和玩家位置组合),每个状态调用辅助 BFS(O(m×n)),总复杂度为 O((m×n)³)。对于 m, n ≤ 20,计算量在可接受范围内。
  • 空间复杂度
    队列和访问集合存储状态,空间复杂度为 O((m×n)²)

该算法通过 双状态跟踪辅助可达性检查,精准模拟推箱子过程,确保最小推动次数的高效求解。核心在于将玩家移动与箱子推动解耦,利用 BFS 天然的最短路径特性,保证了算法的正确性与效率。


网站公告

今日签到

点亮在社区的每一天
去签到