题目
你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights ,其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左上角的格子 (0, 0) ,且你希望去最右下角的格子 (rows-1, columns-1) (注意下标从 0 开始编号)。你每次可以往 上,下,左,右 四个方向之一移动,你想要找到耗费 体力 最小的一条路径。
一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。
请你返回从左上角走到右下角的最小 体力消耗值
示例1
输入:heights = [[1,2,2],[3,8,2],[5,3,5]]
输出:2
解释:路径 [1,3,5,3,5] 连续格子的差值绝对值最大为 2 。
这条路径比路径 [1,2,2,2,5] 更优,因为另一条路径差值最大值为 3 。
题解
1. BFS
- 类似于最短路径
- 在BFS的同时去记录到当前坐标所需要消耗的体力值,由于到达一个坐标有多种方式,故我们用一个数组res去记录到达当前坐标所需的最小体力值
- 初始化队列
- 访问队头元素
- 对于当前元素进行广度优先搜索,计算当前访问点与cur与相邻点next之间的差值是否大于起点到next的最小消耗体力值
- 若小于,则更新res中起点到达next所需要的最小体力值
- 遍历完成后,返回res[row-1][col-1]则表示起点到终点所需的最小体力值
参考代码
class Solution {
public int minimumEffortPath(int[][] heights) {
int row = heights.length, col = heights[0].length;
int[] dx = {1, -1, 0, 0};
int[] dy = {0, 0, 1, -1};
PriorityQueue<int[]> priQueue = new PriorityQueue<int[]>(new Comparator<int[]>() {
public int compare(int[] o1, int[] o2) {
return o1[2] - o2[2];
}
});
priQueue.offer(new int[]{0, 0, 0});
int[][] res = new int[row][col];
for(int[] cur : res) {
Arrays.fill(cur, Integer.MAX_VALUE);
}
res[0][0] = 0;
boolean[][] visited = new boolean[row][col];
while(!priQueue.isEmpty()) {
int[] cur = priQueue.poll();
int x = cur[0];
int y = cur[1];
if(visited[x][y]) continue;
visited[x][y] = true;
// BFS开始
for(int i = 0; i < 4; i++) {
int newX = x + dx[i];
int newY = y + dy[i];
if(newX >= 0 && newX < row && newY >= 0 && newY < col) {
int newD = Math.max(Math.abs(heights[x][y] - heights[newX][newY], res[x][y]));
res[newX][newY] = Math.min(res[newX][newY], newD);
priQueue.offer(new int[]{newX, newY, res[newX][newY]});
}
}
}
return res[newX][newY];
}
}
2. 并查集
- 把每个的格子当前一个点,则整个网格中有row*col个点,每个格子对应的值作为其两点连线的线权
- 这时候问题变成:在线权最小的情况下,连通起点和终点
- 将整个网格内的所有格子,全部转换为:两个点与其线权这种形式,并按照线权进行排序
- 从小开始依次遍历所有的点-边式
- 如果该点-边式的两个点合并为一个连通分量,并判断起点到终点是否属于一个连通分量
- 重复步骤3
参考代码
class Solution {
public int minimumEffortPath(int[][] heights) {
int row = heights.length, col = heights[0].length;
List<int[]> edges = new ArrayList<>();
for(int i = 0; i < row; i++) {
for(int j = 0; j < col; j++) {
int id = i * col + j;
if(i > 0) {
edges.add(new int[]{id - col, id, Math.abs(heights[i][j]-heights[i-1][j])});
}
if(j > 0) {
edges.add(new int[]{id - 1, id, Math.abs(heights[i][j]-heights[i][j-1])})
}
}
}
Collections.sort(edges, new Comparator<int[]>() {
public int compare(int[] o1, int[] o2) {
return o1[2] - o2[2];
}
});
UnionFind myUnionFind = new UnionFind(row * col);
int ans = 0;
for(int[] edge : edges) {
int x = edge[0];
int y = edge[1];
int d = edge[2];
myUnionFind.union(x, y);
if(myUnionFind.isConnected(0, row*col-1)) {
ans = d;
break;
}
}
return ans;
}
class UnionFind{
int[] parent;
int[] rank;
public UnionFind(int n) {
parent = new int[n];
rank = new int[n];
Arrays.fill(rank, 1);
for(int i = 0; i < n; i++) {
parent[i] = i;
}
}
public void union(int a, int b) {
int rootA = find(a);
int rootB = find(b);
if(rootA == rootB) return;
if(rank[rootA] < rank[rootB]) {
parent[rootA] = rootB;
} else {
if(rank[rootA] == rank[rootB]) {
rank[rootA]++;
}
parent[rootB] = rootA;
}
}
public boolean isConnected(int a, int b) {
int rootA = find(a);
int rootB = find(b);
return rootA == rootB;
}
public int find(int index) {
if(index != parent[index]) {
return find(parent[index]);
}
return index;
}
}
}