【Leetcode】1102. Path With Maximum Minimum Value

这篇博客介绍了如何解决寻找从左上角到右下角,路径上数字最小值最大的路径问题。提出了两种方法:一是使用并查集,通过对点按数值排序并尝试连接路径;二是采用修改版的Dijkstra算法,通过优先队列更新最大最小值矩阵。两种方法的时间复杂度均为O(mnlogmn),空间复杂度为O(mn)。
摘要由CSDN通过智能技术生成

题目地址:

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) (m1,n1)这个位置,每一步可以走上下左右四个方向。问在所有的路径中,路径上数字的最小值最大的那条路径,对应的路径最小值是多少。例如,对于下图:在这里插入图片描述
从左上到右下的所有路径中,黄色的路径是最小值最大的那个。题目保证 A A A里的数都是非负的。

法1:并查集。先将每个点的坐标做成一个长度为 2 2 2的数组,然后将这些点按照其对应的值进行排序,数值大者优先。接着遍历这些点,看看什么时候能够组成一条从 ( 0 , 0 ) (0,0) (0,0) ( m − 1 , n − 1 ) (m-1,n-1) (m1,n1)的路径,恰好组成一条路径的时候加入的那个点的数值即为所求。关于如何判断是否形成了一条从起点到终点的路径,可以用并查集来做。代码如下:

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) (m1,n1)这个位置为止,一旦走到这个位置,就可以直接返回 M [ m − 1 ] [ n − 1 ] M[m-1][n-1] M[m1][n1]了。算法正确性证明与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;
    }
}

时空复杂度一样。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值