力扣--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年继续加油!

相关推荐
草履虫建模14 小时前
力扣算法 1768. 交替合并字符串
java·开发语言·算法·leetcode·职场和发展·idea·基础
naruto_lnq16 小时前
分布式系统安全通信
开发语言·c++·算法
Jasmine_llq16 小时前
《P3157 [CQOI2011] 动态逆序对》
算法·cdq 分治·动态问题静态化+双向偏序统计·树状数组(高效统计元素大小关系·排序算法(预处理偏序和时间戳)·前缀和(合并单个贡献为总逆序对·动态问题静态化
qq_2975746716 小时前
【实战教程】SpringBoot 实现多文件批量下载并打包为 ZIP 压缩包
java·spring boot·后端
老毛肚16 小时前
MyBatis插件原理及Spring集成
java·spring·mybatis
学嵌入式的小杨同学16 小时前
【Linux 封神之路】信号编程全解析:从信号基础到 MP3 播放器实战(含核心 API 与避坑指南)
java·linux·c语言·开发语言·vscode·vim·ux
lang2015092816 小时前
JSR-340 :高性能Web开发新标准
java·前端·servlet
Re.不晚16 小时前
Java入门17——异常
java·开发语言
爱吃rabbit的mq17 小时前
第09章:随机森林:集成学习的威力
算法·随机森林·集成学习
缘空如是17 小时前
基础工具包之JSON 工厂类
java·json·json切换