题目描述
你现在手里有一份大小为 N x N 的『地图』(网格) grid,上面的每个『区域』(单元格)都用 0 和 1 标记好了。其中 0 代表海洋,1 代表陆地,你知道距离陆地区域最远的海洋区域是是哪一个吗?请返回该海洋区域到离它最近的陆地区域的距离。
我们这里说的距离是『曼哈顿距离』( Manhattan Distance):(x0, y0) 和 (x1, y1) 这两个区域之间的距离是 |x0 - x1| + |y0 - y1| 。
该题目来源于leetcode,点击进入
读题
示例 :
1 | 0 | 1 |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
输入:[[1,0,1],[0,0,0],[1,0,1]]
输出:2
解释:
海洋区域 (1, 1) 和所有陆地区域之间的距离都达到最大,最大距离为 2。
为什么结果是2呢?根据距离公式可得出除了中间的单元格(1,1)外,其他为0的单元格距离最近的单元格距离都为1,所以(1,1)即为我们需要求得的点。
暴力解题
这里先上一段暴力解题的代码,虽然这样也可以得出正确答案,但是效率太低。
public static int maxDistance(int[][] grid) {
// 距离陆地区域最远的海洋区域到离它最近的陆地区域的距离
int minDistance = 0;
// 将所有海洋和陆地的坐标分别存储
List<int[]> listLand = new ArrayList<int[]>();
List<int[]> ListOcean = new ArrayList<int[]>();
for (int i = 0; i <grid.length ; i++) {
for (int j = 0; j <grid[i].length ; j++) {
if(grid[i][j]==0){
ListOcean.add(new int[]{i,j});
}else{
listLand.add(new int[]{i,j});
}
}
}
// 如果全部都是海洋或者陆地直接返回-1
if(listLand.size()==0 || ListOcean.size()==0){
return -1;
}
for (int i = 0; i < ListOcean.size(); i++) {
int curMinDistance = 0; // 当前循环海洋区域距离其他陆地区域最近的距离
for (int j = 0; j < listLand.size(); j++) {
// 每个陆地与改海洋点位的距离
int distance = Math.abs(listLand.get(j)[0]-ListOcean.get(i)[0]) + Math.abs(listLand.get(j)[1]-ListOcean.get(i)[1]);
// 如果该海洋距离陆地的点位的距离比其他已比较的点位的最小距离还小 则修改距离陆地的最近距离为该距离
// 首次循环直接赋值
curMinDistance = curMinDistance == 0 ? distance : distance < curMinDistance ? distance : curMinDistance;
}
// 如果该海洋点位与其所有陆地的最小距离比其他海洋点位与所有陆地点位的距离还要小,则认为当前点位为距离陆地区域最远的海洋区域
if(minDistance < curMinDistance){
minDistance = curMinDistance;
}
}
return minDistance;
}
BFS解题
我们先找出所有陆地的区域点位,然后向外扩散寻找,每次扩散一步,那么最后扩散到的点位就是我们需要找的最远的那个点。可能这样描述有些抽象,下面用一组图片来说明。我们一层层遍历之后,最终每个点位都遍历完成,此时最后点位即为我们需要求得的点位。
代码
public static int maxDistance(int[][] grid) {
// 对应当前坐标周围的四个坐标
int[] aroundX = {0,0,1,-1};
int[] aroundY = {1,-1,0,0};
// 记录所有陆地区域的点
Queue<int[]> queueLand = new ArrayDeque<int[]>();
int colLength = grid.length;
int rowLength = grid[0].length; // 因为grid中每个元素的长度都相同,这里取第一个
for (int i = 0; i < colLength; i++) {
for (int j = 0; j < rowLength; j++) {
if(grid[i][j] == 1){
queueLand.offer(new int[]{i,j});
}
}
}
// 循环各个陆地 向外扩散 这里运用队列先进先出的特性
boolean isAllLandOrOcean = false;
int[] maxPoint = null;
while (!queueLand.isEmpty()){
maxPoint = queueLand.poll(); // 取出第一个点位的值并从队列中删除
// 遍历周围的四个点 以当前区域对X,Y坐标进行增减可得到周围的四个点
for (int i = 0; i < 4 ; i++) {
int newX = maxPoint[0] + aroundX[i];
int newY = maxPoint[1] + aroundY[i];
// 只对海洋区域的点位操作
if(newX > -1 && newX < colLength && newY > -1 && newY < rowLength && grid[newX][newY] == 0){
isAllLandOrOcean = true; // 能进入这里说明队列肯定不为空且存在未海洋区域的点
// 修改海洋区域的点位为距离最近的陆地的距离 从陆地点位向外扩散 每次以当前节点的值+1
grid[newX][newY] = grid[maxPoint[0]][maxPoint[1]] + 1;
// 把点位加到队列中进行下一轮循环
queueLand.offer(new int[]{newX,newY});
}
}
}
if(maxPoint == null || ! isAllLandOrOcean){
return -1;
}
return grid[maxPoint[0]][maxPoint[1]] - 1;
}