[C++]LeetCode 地图分析 解题思路及详解

该博客详细介绍了如何利用广度优先搜索(BFS)和迪杰斯特拉(Dijkstra)算法解决在给定的01网格中,找出一个海洋单元格到最近陆地单元格的最大曼哈顿距离的问题。通过从每个陆地单元格出发进行多源BFS,并结合Dijkstra算法更新每个海洋单元格的距离,最终找到最大距离并返回。此外,还提供了代码实现和算法流程解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[原题]

你现在手里有一份大小为 n x n 的 网格 grid,上面的每个 单元格 都用 0 和 1 标记好了。其中 0 代表海洋,1 代表陆地。

请你找出一个海洋单元格,这个海洋单元格到离它最近的陆地单元格的距离是最大的,并返回该距离。如果网格上只有陆地或者海洋,请返回 -1。

我们这里说的距离是「曼哈顿距离」( Manhattan Distance):(x0, y0) 和 (x1, y1) 这两个单元格之间的距离是 |x0 - x1| + |y0 - y1| 。

[示例]

示例一
输入:grid = [[1,0,1],[0,0,0],[1,0,1]]
输出:2
解释: 
海洋单元格 (1, 1) 和所有陆地单元格之间的距离都达到最大,最大距离为 2。

示例二
输入:grid = [[1,0,0],[0,0,0],[0,0,0]]
输出:4
解释: 
海洋单元格 (2, 2) 和所有陆地单元格之间的距离都达到最大,最大距离为 4。

[解题思路]

给我们一张网格图,用0和1分别表示海洋和陆地,要求海洋与离它最近的陆地距离的最大值,此处,距离为两个单元格的横坐标之差与纵坐标之差的和

由于所求为海洋与最近的陆地单元格距离,我们不妨以每个陆地单元格为中心点向外搜索,并为每个海洋单元格赋上距离的权值,再取其最大值,即可得到最终的答案。这种方法,就是广度优先搜索(BFS)。

对于一张图的搜索,我们通常采用深度优先搜索(DFS)和广度优先搜索(BFS)两种方式。DFS,即以某一单元格为起点,每次选取临近的某一单元格进行访问,最终通过一条路径到达终点单元格。然后,不断通过回溯来完成对所有单元格的访问。

与DFS不同的是,BFS是以某一单元格为中心,每次访问所有临近的单元格,然后依次扩大访问的范围,直到完成对所有单元格的访问。对于这一过程,可以用如下示意图来演示:

那么,我们该如何实现BFS呢?与二叉树的层序遍历类似,我们需要借助一个特殊的容器——队列。接下来,我们来模拟一下如下图单源BFS的实现:

(1)源节点0入队;

(2)节点0的相邻节点1、2、3、4入队,源节点0出队;

(3)节点1的相邻节点5、6入队,节点1出队;

(4)节点2的相邻节点7、8入队,节点2出队;

(5)节点3的相邻节点9、10入队,节点3出队;

(6)队列中剩余节点都无未访问过的相邻节点,依次出队,结束BFS。

当然,本题中并不是一个单源的BFS,而是从每个陆地节点出发的多源BFS,因此,我们需要统计每个陆地节点的位置,并在(1)步骤中将每个陆地节点入队,其他过程与上述过程相类似。

现在,我们已经确定了搜索的方式,我们又该如何算出每个海洋单元格最近的陆地的距离呢?对于一个有向图的最短路径问题,我们通常有迪杰斯特拉(Dijkstra)算法和弗洛伊德(Floyd)算法两种方式。这里,我们将使用迪杰斯特拉算法。

在一个有向图中,首先从起点开始,访问所有临近的节点,并更新每个节点的权值(若大于原有数值则不更新),直到所有临近节点都被访问过后,选择一个已找到最短路径且权值最小的节点作为下一个起点,重复上述过程。因此,对于每个节点,我们需要一个visited数组来存放是否已取到最短路径(或不存在入度),及一个distance数组来存放源点到每个节点距离的最小值。

让我们用下图的例子来演示一下Dijkstra算法求S节点到所有节点最短路径的过程:

(1)首先从S节点出发,依次访问节点1、2、3,并更新数值。此时1、2、3都没有入度了,visited数组更新为1。

(2)从visited为1且权值最小的3节点开始,访问4节点并更新数值。

(3)从visited为1且权值次小的2节点开始,访问5节点并更新数值。此时5节点没有入度,更新visited数组。

(4)从节点1开始,访问4节点,由于4+5>8,不更新节点。此时4节点没有入度,更新visited数组。

(5)由于4节点的权值小于5节点,我们以4节点为起点,访问6节点并更新数值。所有节点已取得路径最小值,结束遍历。

本题则是对Dijkstra的变形,从多个源点出发,寻找每个单元格的最短路径,且每条边权值都为1。而对于上述的distance数组,我们不妨直接将距离记录在grid数组中,即每次进行BFS时,都将该海洋单元格的数据记录为上一个单元格数据+1。而对于visited数组,我们可以直接判断该海洋单元格是否为0:由于我们的搜索顺序是各个源点顺次交替进行BFS,因此单元格越早更新,所取到的权值越小,所以我们认为,当一个单元格得到更新,则已经取到了最短路径,即已被访问结束,后续不再进行访问。因此当某个海洋单元格为0时,它一定没有被访问结束;当某个海洋单元格不为0时,它一定完成了访问。

另外,由于陆地单元格初值便为1,每个单元格最后所得的距离实际上多算了1,因此返回的最大值也应减去1。

接下来,让我们来看一下整道题的代码(一些细节以批注形式呈现):

//由于队列中需要存储每个单元格的数据,我们不妨定义一个点类来方便存储单元格的横坐标、纵坐标
class Points
{
public:
    int x;
    int y;

    //构造函数
    Points(int _x, int _y) :x(_x), y(_y) {}
};

class Solution
{
public:
    int maxDistance(vector<vector<int>>& grid) {
        int maxDistance = -1;//存放距离的最大值

        //定义dx和dy来分别对应上下左右四个搜索方向
        int dx[4] = { 1,0,-1,0 }, dy[4] = { 0,1,0,-1 };

        //队列的声明,模板类型为刚才定义的Points类
        queue<Points> q;

        //遍历搜索陆地单元格,并放入队列中
        for (int i = 0; i < grid.size(); i++)
        {
            for (int j = 0; j < grid[0].size(); j++)
            {
                if (grid[i][j] == 1)
                    q.push(Points(i, j));
            }
        }

        while (!q.empty())
        {
            //分别取出队首数据的xy坐标
            int x0 = q.front().x, y0 = q.front().y;

            //向四个方向进行搜索
            for (int i = 0; i < 4; i++)
            {
                if (x0 + dx[i] >= 0 && x0 + dx[i] < grid.size() && y0 + dy[i] >= 0 && y0 + dy[i] < grid[0].size() && grid[x0 + dx[i]][y0 + dy[i]] == 0)//越界判断及是否为海洋的判断
                {
                    //更新距离和最大值
                    grid[x0 + dx[i]][y0 + dy[i]] = grid[x0][y0] + 1;
                    maxDistance = max(maxDistance, grid[x0 + dx[i]][y0 + dy[i]]);

                    //将临近节点存入队列
                    q.push(Points(x0 + dx[i], y0 + dy[i]));
                }
            }

            //弹出队首
            q.pop();
        }

        //当全是海洋时,最大值为0,当全是陆地时或只有一个单元格时,最大值为-1,按题目要求返回-1
        if (maxDistance <= 1) return -1;

        //返回最大值
        return maxDistance - 1;
    }
};

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值