题目地址:
https://leetcode.com/problems/path-with-maximum-minimum-value/
给定一个
m
m
m行
n
n
n列的二维矩阵
A
A
A,从
(
0
,
0
)
(0,0)
(0,0)出发要走到
(
m
−
1
,
n
−
1
)
(m-1,n-1)
(m−1,n−1)这个位置,每一步可以走上下左右四个方向。问在所有的路径中,路径上数字的最小值最大的那条路径,对应的路径最小值是多少。例如,对于下图:
从左上到右下的所有路径中,黄色的路径是最小值最大的那个。题目保证
A
A
A里的数都是非负的。
法1:并查集。先将每个点的坐标做成一个长度为 2 2 2的数组,然后将这些点按照其对应的值进行排序,数值大者优先。接着遍历这些点,看看什么时候能够组成一条从 ( 0 , 0 ) (0,0) (0,0)到 ( m − 1 , n − 1 ) (m-1,n-1) (m−1,n−1)的路径,恰好组成一条路径的时候加入的那个点的数值即为所求。关于如何判断是否形成了一条从起点到终点的路径,可以用并查集来做。代码如下:
import java.util.ArrayList;
import java.util.List;
public class Solution {
class UnionFind {
private int[] parent;
private int size;
public UnionFind(int size) {
parent = new int[size];
for (int i = 0; i < parent.length; i++) {
parent[i] = i;
}
this.size = size;
}
public int find(int x) {
if (x != parent[x]) {
parent[x] = find(parent[x]);
}
return parent[x];
}
public void union(int x, int y) {
int px = find(x), py = find(y);
if (px == py) {
return;
}
parent[px] = py;
}
}
public int maximumMinimumPath(int[][] A) {
int m = A.length, n = A[0].length;
// 存一下所有的点
List<int[]> list = new ArrayList<>();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
list.add(new int[]{i, j});
}
}
// 按照数值从大到小排序
list.sort((p1, p2) -> -Integer.compare(A[p1[0]][p1[1]], A[p2[0]][p2[1]]));
UnionFind uf = new UnionFind(m * n);
int[] dir = {1, 0, -1, 0, 1};
// 记录哪些点已经被选取了
boolean[][] visited = new boolean[m][n];
// 算一下起始点和终点对应的一维坐标
int start = 0, end = m * n - 1;
for (int i = 0; i < list.size(); i++) {
int[] pair = list.get(i);
int x = pair[0], y = pair[1];
// 标记这个位置的数已经用过了
visited[x][y] = true;
for (int j = 0; j < 4; j++) {
int nextX = x + dir[j], nextY = y + dir[j + 1];
// 将当前点和其四周使用过的点连起来
if (0 <= nextX && nextX < m && 0 <= nextY && nextY < n && visited[nextX][nextY]) {
uf.union(transform(x, y, n), transform(nextX, nextY, n));
}
// 如果发现加上了当前点之后,形成了一条从起点到终点的路径,那么当前点即为所求
if (uf.find(start) == uf.find(end)) {
return A[x][y];
}
}
}
return 0;
}
// 作用是将二维坐标转为一维
private int transform(int x, int y, int n) {
return x * n + y;
}
}
时间复杂度 O ( m n log ( m n ) ) O(mn\log (mn)) O(mnlog(mn)),空间 O ( m n ) O(mn) O(mn)。
法2:修改版Dijkstra。将每个位置和能到达该位置的路径的最大最小值 v v v一起做成一个pair,再开一个二维数组 M M M,使得 M [ i ] [ j ] M[i][j] M[i][j]就是从 ( 0 , 0 ) (0,0) (0,0)出发到达 ( i , j ) (i,j) (i,j)的所有路径中最小值最大的那条路径对应的最小值,一开始 M M M赋值为 0 0 0(因为题目保证 A A A非负。当然这里赋值为负无穷也是可以的)。接下来模仿Dijkstra算法来做。我们可以简称是PFS,即Priority First Search。开一个最小堆,这个堆按照pair的 v v v值规定优先级, v v v大者优先出堆(因为我们要找到最大的最小值),先把 ( 0 , 0 , A [ 0 ] [ 0 ] ) (0,0,A[0][0]) (0,0,A[0][0])这个pair加入堆中,含义是走到 ( 0 , 0 ) (0,0) (0,0)位置的路径的最大最小值是 A [ 0 ] [ 0 ] A[0][0] A[0][0],这是合理的,同时也将 M [ 0 ] [ 0 ] M[0][0] M[0][0]赋值为 A [ 0 ] [ 0 ] A[0][0] A[0][0]。接下来while循环,当堆不空的时候,则出堆,将出堆点的位置标记为访问过(在Dijkstra算法里也有类似操作,表示到这个位置的最短路已求出。Dijkstra算法能保证每次出堆的时候,出堆的点的最短路一定已经求出),同时用这个点去更新其邻居 A [ x ] [ y ] A[x][y] A[x][y],先算一下这个点对应的最大最小值和 A [ x ] [ y ] A[x][y] A[x][y]值哪个更小,然后取小的,比如是 z z z,然后再比较 z z z与 M [ x ] [ y ] M[x][y] M[x][y],如果 z > M [ x ] [ y ] z>M[x][y] z>M[x][y],说明找到了另一条路,可以使得到达 ( x , y ) (x,y) (x,y)这个位置的路径上的最小值要大于 M [ x ] [ y ] M[x][y] M[x][y],那么将 M [ x ] [ y ] M[x][y] M[x][y]做更新,然后将 ( x , y , M [ x ] [ y ] ) (x,y,M[x][y]) (x,y,M[x][y])这个pair入堆。这样直到走到 ( m − 1 , n − 1 ) (m-1,n-1) (m−1,n−1)这个位置为止,一旦走到这个位置,就可以直接返回 M [ m − 1 ] [ n − 1 ] M[m-1][n-1] M[m−1][n−1]了。算法正确性证明与Dijkstra算法类似。代码如下:
import java.util.PriorityQueue;
public class Solution {
class Pair {
int x, y, maxMin;
public Pair(int x, int y, int maxMin) {
this.x = x;
this.y = y;
this.maxMin = maxMin;
}
}
public int maximumMinimumPath(int[][] A) {
int m = A.length, n = A[0].length;
int[][] maxMinMat = new int[m][n];
boolean[][] visited = new boolean[m][n];
PriorityQueue<Pair> maxHeap = new PriorityQueue<>((p1, p2) -> -Integer.compare(p1.maxMin, p2.maxMin));
// 将起点入堆,并更新起点
maxHeap.offer(new Pair(0, 0, A[0][0]));
maxMinMat[0][0] = A[0][0];
int[] dir = {1, 0, -1, 0, 1};
while (!maxHeap.isEmpty()) {
Pair cur = maxHeap.poll();
int x = cur.x, y = cur.y;
// 如果走到了右下角,那已经找到了答案,返回之
if (x == m - 1 && y == n - 1) {
return maxMinMat[x][y];
}
// 标记当前位置的最大最小值已经求出
visited[x][y] = true;
for (int i = 0; i < 4; i++) {
int nextX = x + dir[i], nextY = y + dir[i + 1];
// 这里全是模仿Dijkstra算法来做了
if (0 <= nextX && nextX < m && 0 <= nextY && nextY < n && !visited[nextX][nextY]) {
int curMaxMin = Math.min(cur.maxMin, A[nextX][nextY]);
if (maxMinMat[nextX][nextY] < curMaxMin) {
maxMinMat[nextX][nextY] = curMaxMin;
maxHeap.offer(new Pair(nextX, nextY, curMaxMin));
}
}
}
}
return 0;
}
}
时空复杂度一样。