先来看下题目:
看到题目后,先想到了BFS的做法,但是却找错了“中心点”;我一开始是将海洋作为遍历的中心点,然后找每个海洋与所有陆地的距离后求出最小距离,然后等到所有海洋都被遍历完后,取之前求过的最小距离中的最大值即为答案,代码是这样的:
class Solution {
public:
int maxDistance(vector<vector<int>>& grid) {
if(grid.size()==0)return 0;
queue<pair<int,int>>sea;
vector<pair<int,int>>land;
for(int i=0;i<grid.size();i++){
for(int j=0;j<grid[0].size();j++){
if(grid[i][j]==1)land.push_back({i,j});
else if(grid[i][j]==0)sea.push({i,j});
}
}
if(sea.size()==0||land.size()==0)return -1;
vector<int>mindistance;
int distance=0;
while(!sea.empty()){
int temp=INT_MAX;
for(auto l:land){
int x=abs(sea.front().first-l.first);
int y=abs(sea.front().second-l.second);
temp=min(temp,x+y);
}
sea.pop();
distance=max(distance,temp);
}
return distance;
}
};
测试用例通过,然后提交,果然超时。
直到我看了最后的测试用例(如下图)明白了一切:当地图非常大时,这种以海洋为中心的BFS会显得非常复杂且低效。那么,能不能换个思路呢?
看了评论区的解答后,发现另一种做法是以“陆地”为中心的“链式扩展”BFS,也就是“填海造陆”的做法,这种做法效率显然要高得多。
那么为什么可以这么做呢?稍加思考之后我们可以发现这题跟LeetCode上“腐烂的橘子”本质上是换汤不换药的两个题目,这道题我们先把陆地(坏橘子)加入队列里,然后判断是否全部是陆地(坏橘子)或海洋(新鲜橘子);最后进行BFS搜索,遇到与陆地相邻的海洋时,将其数字修改并加入陆地队列(腐烂新鲜橘子),直到最后没有海洋存在,返回遍历的层数即为所求最大距离。这样的过程我们可以这样来直观的理解:开始时我可以从任意一块陆地出发(最开始加入队列的陆地),经过一步我可以到达哪些海洋(初始队列紧邻的海洋),然后我再以这些点出发进过一步又可以到达哪些海洋,因为我们每一步都是选择紧邻自己的海洋,所以到达每个海洋我都是挑所有可路线里最近的那条来走,所以说这样来走的话,最后才走到的那个海洋一定是离所有陆地的最近距离是最远的(否则我不会最后一个到达它)。
我们都知道BFS的代码框架式这样的:(几乎所有求最短路径的问题,都可以用BFS解决)
while queue 非空:
node = queue.pop()
for node 的所有相邻结点 m:
if m 未访问过:
queue.push(m)
而为了做到层序遍历,我们必须保证当前层遍历完成后才继续下一层,如果用上面这种写法来遍历的话,我们是无法区分 BFS 遍历中的每一“层”的。这是因为,遍历的时候,第 1 层的结点还没出完队列,第 2 层的结点就进来了。这个队列中第 1 层和第 2 层的结点会紧挨在一起,无法区分,也就无法知道每个结点的距离 depth 了。
depth = 0 # 记录遍历到第几层
while queue 非空:
depth++
n = queue 中的元素个数
循环 n 次:
node = queue.pop()
for node 的所有相邻结点 m:
if m 未访问过:
queue.push(m)
而在此题中,因为初始队列是陆地(还没开始走),所以depth=-1;例外要注意地图边界情况。
public int maxDistance(int[][] grid) {
int N = grid.length;
Queue<int[]> queue = new ArrayDeque<>();
// 将所有的陆地格子加入队列
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (grid[i][j] == 1) {
queue.add(new int[]{i, j});
}
}
}
// 如果我们的地图上只有陆地或者海洋,请返回 -1。
if (queue.isEmpty() || queue.size() == N * N) {
return -1;
}
int distance = -1;
while (!queue.isEmpty()) {
distance++;
int n = queue.size();
// 这里一口气取出 n 个结点,以实现层序遍历
for (int i = 0; i < n; i++) {
int[] cell = queue.poll();
int r = cell[0];
int c = cell[1];
// 遍历上方单元格
if (r-1 >= 0 && grid[r-1][c] == 0) {
grid[r-1][c] = 2;
queue.add(new int[]{r-1, c});
}
// 遍历下方单元格
if (r+1 < N && grid[r+1][c] == 0) {
grid[r+1][c] = 2;
queue.add(new int[]{r+1, c});
}
// 遍历左边单元格
if (c-1 >= 0 && grid[r][c-1] == 0) {
grid[r][c-1] = 2;
queue.add(new int[]{r, c-1});
}
// 遍历右边单元格
if (c+1 < N && grid[r][c+1] == 0) {
grid[r][c+1] = 2;
queue.add(new int[]{r, c+1});
}
}
}
return distance;
}