LeetCode-934. 最短的桥

1、题目描述:

给你一个大小为 n x n 的二元矩阵 grid ,其中 1 表示陆地,0 表示水域。

 是由四面相连的 1 形成的一个最大组,即不会与非组内的任何其他 1 相连。grid 中 恰好存在两座岛 。

你可以将任意数量的 0 变为 1 ,以使两座岛连接起来,变成 一座岛 。

返回必须翻转的 0 的最小数目。

示例 1:

输入:grid = [[0,1],
              [1,0]]
输出:1

示例 2:

输入:grid = [[0,1,0],
              [0,0,0],
              [0,0,1]]
输出:2

示例 3:

输入:grid = [[1,1,1,1,1],
              [1,0,0,0,1],
              [1,0,1,0,1],
              [1,0,0,0,1],
              [1,1,1,1,1]]
输出:1

提示:

  • n == grid.length == grid[i].length
  • 2 <= n <= 100
  • grid[i][j] 为 0 或 1
  • grid 中恰有两个岛

2、代码:

/*
 * @file shortest_bridge.cpp
 * @brief 在二进制矩阵中找到连接两个岛屿的最短路径
 * 
 * 给定一个 n x n 的二进制矩阵 grid,其中 1 表示陆地,0 表示水域。
 * 岛屿是由四面相连的 1 组成的组。假设矩阵中有且仅有两个岛屿,
 * 需要找到连接两个岛屿的最短桥(即翻转最少数量的0使两个岛屿相连)。
 * 
 * 实现思路:
 *  1. 使用BFS标记第一个岛屿的所有单元格(标记为2)
 *  2. 从第一个岛屿的所有边界开始进行多源BFS扩展
 *  3. 当遇到第二个岛屿(值为1的单元格)时,返回扩展的步数
 * 
 */

#include <vector>
#include <queue>

using namespace std;

class Solution {
public:
    int shortestBridge(vector<vector<int>>& grid) {
        int n = grid.size();
        // 定义四个移动方向:上、下、左、右
        vector<vector<int>> dirs = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} };
        // 主队列:用于后续的多源BFS扩展
        queue<pair<int, int>> q;

        // 步骤1: 找到并标记第一个岛屿
        bool found = false; // 标记是否已找到第一个岛屿
        for (int i = 0; i < n && !found; ++i) {
            for (int j = 0; j < n && !found; ++j) {
                if (grid[i][j] == 1) {  // 发现第一个岛屿的起始点
                    // 使用BFS标记整个岛屿
                    queue<pair<int, int>> islandQ; // 岛屿内部BFS专用队列
                    islandQ.push({ i, j });
                    grid[i][j] = 2;     // 将起始点标记为2(表示属于第一个岛屿)
                    q.push({ i, j });   // 同时加入主队列(作为多源BFS的起点)
                    
                    // BFS遍历并标记第一个岛屿的所有单元格
                    while (!islandQ.empty()) {
                        auto [x, y] = islandQ.front();
                        islandQ.pop();
                        // 检查四个方向
                        for (auto& dir : dirs) {
                            int nx = x + dir[0], ny = y + dir[1];
                            // 检查新位置是否在网格内且属于第一个岛屿(值为1)
                            if (nx >= 0 && ny >= 0 && nx < n && ny < n && grid[nx][ny] == 1) {
                                grid[nx][ny] = 2;   // 标记为属于第一个岛屿
                                islandQ.push({ nx, ny }); // 加入岛屿BFS队列
                                q.push({ nx, ny }); // 加入主队列(作为扩展起点)
                            }
                        }
                    }
                    found = true; // 标记第一个岛屿已处理完成
                }
            }
        }

        // 步骤2: 从第一个岛屿边界开始进行多源BFS扩展
        int step = 0; // 记录扩展的步数(即桥的长度)
        while (!q.empty()) {
            int size = q.size(); // 当前层的节点数量
            // 遍历当前层的所有节点
            for (int i = 0; i < size; ++i) {
                auto [x, y] = q.front();
                q.pop();
                // 向四个方向扩展
                for (auto& dir : dirs) {
                    int nx = x + dir[0], ny = y + dir[1];
                    // 检查新位置是否在网格内
                    if (nx >= 0 && ny >= 0 && nx < n && ny < n) {
                        if (grid[nx][ny] == 1) {
                            // 找到第二个岛屿,返回当前步数(即桥的最小长度)
                            return step;
                        }
                        else if (grid[nx][ny] == 0) {
                            // 遇到水域,标记为已访问(避免重复处理)
                            grid[nx][ny] = 2;
                            // 加入队列继续扩展
                            q.push({ nx, ny });
                        }
                    }
                }
            }
            step++; // 完成一层扩展,步数增加
        }

        return -1; // 理论上不会执行(因为保证有两个岛屿)
    }
};

3、解题思路:

  1. 识别第一个岛屿

    • 遍历矩阵,找到第一个值为 1 的点,使用 BFS 或 DFS 遍历其所有相连的 1,标记为第一个岛屿。
    • 在这个过程中,可以使用一个 dist 数组来记录每个点的距离,初始岛屿的点距离为 0
  2. BFS 扩展第一个岛屿

    • 从第一个岛屿的所有点出发,进行 BFS 扩展,逐步向外探索。
    • 每次扩展一个 0,将其标记为已访问,并记录当前扩展的步数。
    • 当扩展过程中遇到另一个岛屿的 1 时,此时的步数即为需要转换的最小水域数量。
  3. 返回结果

    • 一旦找到连接两个岛屿的最短路径,立即返回该路径长度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值