力扣--1970. 你能穿过矩阵的最后一天(Java)

目录

前言:

题目:

[示例 1:](#示例 1:)

[示例 2:](#示例 2:)

[示例 3:](#示例 3:)

提示:

题目分析:

代码:

代码分析:

[步骤 1:二分查找天数](#步骤 1:二分查找天数)

[步骤 2:对每个mid构建地图并判断是否连通(关键)](#步骤 2:对每个mid构建地图并判断是否连通(关键))

实现细节注意

结语:


前言:

今年是2025年最后一天,祝大家身体健康,天天开心,学业有成,事业有成!
新的一年新的成绩

这是一道是一个经典的"最后可行时间"类问题,可以使用二分查找 + BFS,希望可以给大家提供帮助!

题目:

给你一个下标从 1 开始的二进制矩阵,其中 0 表示陆地,1 表示水域。同时给你 row 和 col 分别表示矩阵中行和列的数目。

一开始在第 0 天,整个 矩阵都是 陆地 。但每一天都会有一块新陆地被 水 淹没变成水域。给你一个下标从 1 开始的二维数组 cells ,其中 cells[i] = [ri, ci] 表示在第 i 天,第 ri 行 ci 列(下标都是从 1 开始)的陆地会变成 水域 (也就是 0 变成 1 )。

你想知道从矩阵最 上面 一行走到最 下面 一行,且只经过陆地格子的 最后一天 是哪一天。你可以从最上面一行的 任意 格子出发,到达最下面一行的 任意 格子。你只能沿着 四个 基本方向移动(也就是上下左右)。

请返回只经过陆地格子能从最 上面 一行走到最 下面 一行的 最后一天 。

示例 1:

输入:row = 2, col = 2, cells = [[1,1],[2,1],[1,2],[2,2]]

输出:2

解释:上图描述了矩阵从第 0 天开始是如何变化的。

可以从最上面一行到最下面一行的最后一天是第 2 天。

示例 2:

输入:row = 2, col = 2, cells = [[1,1],[1,2],[2,1],[2,2]]

输出:1

解释:上图描述了矩阵从第 0 天开始是如何变化的。

可以从最上面一行到最下面一行的最后一天是第 1 天。

示例 3:

输入:row = 3, col = 3, cells = [[1,2],[2,1],[3,3],[2,2],[1,1],[1,3],[2,3],[3,2],[3,1]]

输出:3

解释:上图描述了矩阵从第 0 天开始是如何变化的。

可以从最上面一行到最下面一行的最后一天是第 3 天。

提示:

2 <= row, col <= 2 * 104

4 <= row * col <= 2 * 104

cells.length == row * col

1 <= ri <= row

1 <= ci <= col

cells 中的所有格子坐标都是 唯一 的。


题目分析:

读完题目,看完例题,我希望大家可以把握住一个点,一个非常核心的思想:

随着天数增加,陆地越来越少(水域越来越多),通行能力只会变差,不会变好

基于这个,我们就可以使用二分查找,因为

  1. 如果第 d 天还能从上走到下 → 那么第 1, 2, ..., d-1 天也一定可以,因为那时水更少,陆地更多
  2. 如果第 d 天不能走通 → 那么第 d+1, d+2, ... 天也一定不能,因为水只会更多

代码:

java 复制代码
class Solution {
    public int latestDayToCross(int row, int col, int[][] cells) {
        int left = 0;
        int right = cells.length; // 最多可能到第 n 天仍可通行
        int result = 0;

        // 二分查找:找最大的可行天数
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (canCross(row, col, cells, mid)) {
                result = mid;      // 记录可行答案
                left = mid + 1;    // 尝试更晚的天数
            } else {
                right = mid - 1;   // 太晚了,往前找
            }
        }
        return result;
    }

    // 判断在第 day 天(即前 day 个 cells 被淹没)是否还能从上走到下
    private boolean canCross(int row, int col, int[][] cells, int day) {
        // 初始化网格:0 表示陆地,1 表示水域
        int[][] grid = new int[row][col];
        
        // 把前 day 天淹没的格子设为水域(注意:cells 是 1-indexed)
        for (int i = 0; i < day; i++) {
            int r = cells[i][0] - 1; // 转为 0-index
            int c = cells[i][1] - 1;
            grid[r][c] = 1; // 变成水
        }

        // BFS:从第一行所有陆地出发
        Queue<int[]> queue = new LinkedList<>();
        boolean[][] visited = new boolean[row][col];
        
        // 初始化:第一行的陆地入队
        for (int j = 0; j < col; j++) {
            if (grid[0][j] == 0) {
                queue.offer(new int[]{0, j});
                visited[0][j] = true;
            }
        }

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

        while (!queue.isEmpty()) {
            int[] cur = queue.poll();
            int x = cur[0], y = cur[1];

            // 如果到达最后一行,成功!
            if (x == row - 1) {
                return true;
            }

            // 探索四个邻居
            for (int[] dir : dirs) {
                int nx = x + dir[0];
                int ny = y + dir[1];

                // 边界检查 + 是否是陆地 + 是否未访问
                if (nx >= 0 && nx < row && ny >= 0 && ny < col
                    && grid[nx][ny] == 0 && !visited[nx][ny]) {
                    visited[nx][ny] = true;
                    queue.offer(new int[]{nx, ny});
                }
            }
        }

        return false; // 无法到达底部
    }
}

代码分析:

步骤 1:二分查找天数

  1. 左边界 left = 0
  2. 右边界 right = cells.length(最多能走到第 n 天)
  3. 我们要找最大的 d,使得第 d 天还能走通。
  4. 为什么 right 是 cells.length?因为第 cells.length 天表示所有给定的格子都淹了,但可能仍能走通
java 复制代码
        int left = 0;
        int right = cells.length; // 最多可能到第 n 天仍可通行
        int result = 0;

        // 二分查找:找最大的可行天数
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (canCross(row, col, cells, mid)) {
                result = mid;      // 记录可行答案
                left = mid + 1;    // 尝试更晚的天数
            } else {
                right = mid - 1;   // 太晚了,往前找
            }
        }
        return result;
    }

步骤 2:对每个mid构建地图并判断是否连通(关键)

  1. 创建一个 row × col 的网格,初始全为陆地(0)。
  2. 把前 mid 天的 cells[0] 到 cells[mid-1] 标记为水域(1)。
  3. 然后从第一行所有陆地格子出发,做 BFS 或 DFS,看能否到达最后一行任意陆地格子。
  4. 如果能 → 说明第 mid 天可行,尝试更大的天数(left = mid + 1)
  5. 如果不能 → 说明太晚了,得往前找(right = mid - 1)
java 复制代码
    // 判断在第 day 天(即前 day 个 cells 被淹没)是否还能从上走到下
    private boolean canCross(int row, int col, int[][] cells, int day) {
        // 初始化网格:0 表示陆地,1 表示水域
        int[][] grid = new int[row][col];
        
        // 把前 day 天淹没的格子设为水域(注意:cells 是 1-indexed)
        for (int i = 0; i < day; i++) {
            int r = cells[i][0] - 1; // 转为 0-index
            int c = cells[i][1] - 1;
            grid[r][c] = 1; // 变成水
        }

        // BFS:从第一行所有陆地出发
        Queue<int[]> queue = new LinkedList<>();
        boolean[][] visited = new boolean[row][col];
        
        // 初始化:第一行的陆地入队
        for (int j = 0; j < col; j++) {
            if (grid[0][j] == 0) {
                queue.offer(new int[]{0, j});
                visited[0][j] = true;
            }
        }

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

        while (!queue.isEmpty()) {
            int[] cur = queue.poll();
            int x = cur[0], y = cur[1];

            // 如果到达最后一行,成功!
            if (x == row - 1) {
                return true;
            }

            // 探索四个邻居
            for (int[] dir : dirs) {
                int nx = x + dir[0];
                int ny = y + dir[1];

                // 边界检查 + 是否是陆地 + 是否未访问
                if (nx >= 0 && nx < row && ny >= 0 && ny < col
                    && grid[nx][ny] == 0 && !visited[nx][ny]) {
                    visited[nx][ny] = true;
                    queue.offer(new int[]{nx, ny});
                }
            }
        }

        return false; // 无法到达底部
    }

实现细节注意

  1. 下标转换

    • 题目说 cells[i] = [ri, ci]1-indexed
    • 代码中数组是 0-indexed → 要减 1:(ri - 1, ci - 1)
  2. BFS/DFS 起点

    • 把第一行所有 grid[0][j] == 0(陆地)加入队列
    • 终止条件:走到任意 grid[row-1][j] == 0
  3. 方向数组

    java 复制代码
    int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}};

结语:

这个题目也可以用并查集(Union-Find)逆序处理:从最后一天开始,逐步"恢复"陆地,直到上下连通。这也是常见解法。但我个人认为二分 + BFS 更直观,容易写对,推荐先掌握这个!

大家有兴趣可以搜一下解法!我这里就不赘述了!希望可以帮助到大家!我们2026年继续加油!

相关推荐
少年执笔2 小时前
android新版TTS无法进行语音播报
android·java
AndrewHZ2 小时前
【图像处理基石】如何高质量地生成一张庆祝元旦的图片?
图像处理·人工智能·opencv·算法·计算机视觉·生成式模型·genai
光明西道45号2 小时前
Leetcode 15. 三数之和
数据结构·算法·leetcode
咸鱼2.02 小时前
【java入门到放弃】数据结构
java·开发语言·数据结构
啊西:2 小时前
SuperMap iObjects Java地图生成栅格瓦片并保存到mongodb
java·开发语言·mongodb
教练、我想打篮球2 小时前
125 如何运行时实时切换数据库(实时切换影子库)
java·spring·shadow·datasource
毕设源码-赖学姐2 小时前
【开题答辩全过程】以 “实惠”药房管理系统为例,包含答辩的问题和答案
java
共享家95272 小时前
测试常用函数(一)
java·前端·javascript
廋到被风吹走2 小时前
【JAVA】【JDK】java8版本之后各个版本调整
java·开发语言