青训营X豆包MarsCode技术训练营 | 豆包MarsCode AI刷题

103 阅读4分钟

小S的黑白快迷宫【难】

问题描述

小S在一个n×m的网格迷宫中,初始位置在左上角 (1,1),目标是到达右下角 (n,m)。每个格子可以是黑色(表示为1)或者白色(表示为0)。他希望在移动过程中经过的黑色格子尽可能少。移动时可以向上、下、左、右四个方向移动,但不能走出迷宫的边界。请你帮小S计算从起点到终点所需经过的最少黑色格子的数量。


测试样例

样例1:

输入:n = 5 ,m = 3 ,grid = [[0, 1, 0], [0, 1, 1], [0, 1, 0], [1, 0, 0], [1, 0, 0]]
输出:1

样例2:

输入:n = 4 ,m = 4 ,grid = [[0, 0, 1, 0], [1, 0, 1, 0], [1, 0, 0, 0], [1, 1, 1, 0]]
输出:0

样例3:

输入:n = 3 ,m = 3 ,grid = [[0, 0, 0], [1, 1, 0], [1, 1, 0]]
输出:0

解题思路

  1. 广度优先搜索 (BFS)
  • 使用 BFS 来寻找从起点到终点的最短路径。
  • 在 BFS 过程中,记录每个节点的当前步数和经过的黑色格子数量。
  • 使用一个队列来存储当前的状态,包括当前位置、步数和经过的黑色格子数量。
  • 使用一个二维数组 visited 来记录每个位置是否已经被访问过,避免重复访问。
    • 从队列中取出当前状态。
    • 如果到达终点,返回当前的黑色格子数量。
    • 遍历四个方向,检查新的位置是否在边界内且未被访问过。
    • 计算新的黑色格子数量,并将新状态入队,同时标记为已访问。
    while (!queue.isEmpty()) {
        int[] current = queue.poll();
        int x = current[0];
        int y = current[1];
        int blackCount = current[2];
        
        // 到达终点
        if (x == n - 1 && y == m - 1) {
            return blackCount;
        }
        
        // 遍历四个方向
        for (int[] dir : directions) {
            int newX = x + dir[0];
            int newY = y + dir[1];
            
            // 检查边界条件
            if (newX >= 0 && newX < n && newY >= 0 && newY < m && !visited[newX][newY]) {
                int newBlackCount = blackCount + (grid[newX][newY] == 1 ? 1 : 0);
                queue.offer(new int[]{newX, newY, newBlackCount});
                visited[newX][newY] = true;
            }
        }
    }
    
  1. 状态表示
  • 每个状态可以用一个三元组 (x, y, blackCount) 表示,其中 (x, y) 是当前的位置,blackCount 是经过的黑色格子数量。
    • 方向数组
    int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
    
    • 初始化队列和访问数组:使用队列 queue 来存储当前的状态。使用二维数组 visited 来记录每个位置是否已经被访问过。
    Queue<int[]> queue = new LinkedList<>();
    boolean[][] visited = new boolean[n][m];
    
  1. 终止条件
  • 当到达终点 (n,m)(n,m) 时,返回当前经过的黑色格子数量。
  • 如果队列为空且未找到终点,返回 -1 表示无法到达终点
  1. 优化
  • 如果当前状态已经访问过,并且当前状态的黑色格子数量不小于之前访问过的状态的黑色格子数量,则跳过该状态。

图解

假设有一个 5x3 的网格迷宫,如下所示:

    0 1 0
    0 1 1
    0 1 0
    1 0 0
    1 0 0
  • 初始状态:(1, 1, 0)
  • 第一步:向右移动到 (1, 2, 1)
  • 第二步:向下移动到 (2, 2, 2)
  • 第三步:向右移动到 (2, 3, 3)
  • 第四步:向下移动到 (3, 3, 3)
  • 第五步:向下移动到 (4, 3, 3)
  • 第六步:向下移动到 (5, 3, 3)

在这个例子中,最优路径是经过 1 个黑色格子。

完整代码

import java.util.*;

public class Main {
    public static int solution(int n, int m, int[][] grid) {
        // 方向数组,表示上下左右四个方向
        int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
        
        // 初始化队列和访问数组
        Queue<int[]> queue = new LinkedList<>();
        boolean[][] visited = new boolean[n][m];
        
        // 起点入队
        queue.offer(new int[]{0, 0, 0}); // (x, y, blackCount)
        visited[0][0] = true;
        
        while (!queue.isEmpty()) {
            int[] current = queue.poll();
            int x = current[0];
            int y = current[1];
            int blackCount = current[2];
            
            // 到达终点
            if (x == n - 1 && y == m - 1) {
                return blackCount;
            }
            
            // 遍历四个方向
            for (int[] dir : directions) {
                int newX = x + dir[0];
                int newY = y + dir[1];
                
                // 检查边界条件
                if (newX >= 0 && newX < n && newY >= 0 && newY < m && !visited[newX][newY]) {
                    int newBlackCount = blackCount + (grid[newX][newY] == 1 ? 1 : 0);
                    queue.offer(new int[]{newX, newY, newBlackCount});
                    visited[newX][newY] = true; // 标记为已访问
                }
            }
        }
        
        // 如果无法到达终点
        return -1;
    }

    public static void main(String[] args) {
        System.out.println(solution(5, 3, new int[][]{{0, 1, 0}, {0, 1, 1}, {0, 1, 0}, {1, 0, 0}, {1, 0, 0}}) == 1);
        System.out.println(solution(4, 4, new int[][]{{0, 0, 1, 0}, {1, 0, 1, 0}, {1, 0, 0, 0}, {1, 1, 1, 0}}) == 0);
        System.out.println(solution(3, 3, new int[][]{{0, 0, 0}, {1, 1, 0}, {1, 1, 0}}) == 0);
    }
}