【Lintcode】1418. Path With Maximum Minimum Value

题目地址:

https://www.lintcode.com/problem/path-with-maximum-minimum-value/description

给定一个 m × n m\times n m×n的二维矩阵 A A A,找到从 A [ 0 ] [ 0 ] A[0][0] A[0][0] A [ m − 1 ] [ n − 1 ] A[m-1][n-1] A[m1][n1]的路径,使得路径上的最小值最大。路径每一步可以走四个方向,但同一个格子不能重复走。返回该路径的最小值。

一个比较好理解也比较好写的方法是用并查集来做。思路参考https://blog.csdn.net/qq_46105170/article/details/109713256。思路非常像Kruskal最小生成树算法。代码如下:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Solution {
    
    class UnionFind {
        private int[] parent;
        
        public UnionFind(int size) {
            parent = new int[size];
            Arrays.fill(parent, -1);
        }
        
        public void insert(int x) {
            parent[x] = x;
        }
        
        public int find(int x) {
            if (parent[x] != 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 boolean exists(int x) {
            return parent[x] != -1;
        }
    }
    
    class Pair {
        int x, y, val;
        
        public Pair(int x, int y, int val) {
            this.x = x;
            this.y = y;
            this.val = val;
        }
    }
    
    /**
     * @param A: a List[List[int]]
     * @return: Return the maximum score of a path
     */
    public int maximumMinimumPath(int[][] A) {
        // Write your code here
        int m = A.length, n = A[0].length;
        UnionFind uf = new UnionFind(m * n);
        List<Pair> list = new ArrayList<>();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                list.add(new Pair(i, j, A[i][j]));
            }
        }
        
        int start = 0, end = m * n - 1;
        int[] d = {1, 0, -1, 0, 1};
        // 按权值从大到小排序
        list.sort((p1, p2) -> -Integer.compare(p1.val, p2.val));
        
        for (int i = 0; i < list.size(); i++) {
            Pair pair = list.get(i);
            int x = pair.x, y = pair.y;
            uf.insert(transfer(x, y, n));
            for (int j = 0; j < 4; j++) {
                int nextX = x + d[j], nextY = y + d[j + 1];
                if (0 <= nextX && nextX < m && 0 <= nextY && nextY < n && uf.exists(transfer(nextX, nextY, n))) {
                    uf.union(transfer(x, y, n), transfer(nextX, nextY, n));
                }
            }
            
            if (uf.exists(start) && uf.exists(end) && uf.find(start) == uf.find(end)) {
                return pair.val;
            }
        }
        
        return -1;
    }
    
    private int transfer(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)

上面的做法是逐个添加点,然后慢慢让起点和终点连通,一旦连通了,那么添加的那个点的值即为所求。但这样做似乎并不是Kruskal算法,说服力不够(事实上尽管与Kruskal算法看上去不像,但是也可以像证明Kruskal算法的方法去证明这个方法是对的)。下面介绍逐个添加边的办法。考虑每条边 e e e的权重应该是多少,显然如果要走 e e e,那么路径的最小值必然会小于等于连接 e e e的两个顶点的点权较小值,所以我们规定如果 e = ( x , y ) e=(x,y) e=(x,y) e = max ⁡ { x , y } e=\max\{x,y\} e=max{x,y}。这样就可以将整个矩阵每个entry看成点,相邻entry看成边,整个矩阵看成一张图,然后做Kruskal算法(边权从大到小排序)。代码如下:

import java.util.ArrayList;
import java.util.List;

public class Solution {
    
    class Edge {
        int x, y, len;
        
        public Edge(int x, int y, int len) {
            this.x = x;
            this.y = y;
            this.len = len;
        }
    }
    
    class UnionFind {
        int[] parent;
        
        public UnionFind(int size) {
            parent = new int[size];
            for (int i = 0; i < size; i++) {
                parent[i] = i;
            }
        }
        
        public int find(int x) {
            if (parent[x] != 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) {
                parent[px] = py;
            }
        }
    }
    
    /**
     * @param A: a List[List[int]]
     * @return: Return the maximum score of a path
     */
    public int maximumMinimumPath(int[][] A) {
        // Write your code here
        if (A.length == 1 && A[0].length == 1) {
            return A[0][0];
        }
        
        List<Edge> list = new ArrayList<>();
        for (int i = 0; i < A.length; i++) {
            for (int j = 0; j < A[0].length; j++) {
                if (j + 1 < A[0].length) {
                    list.add(new Edge(transform(i, j, A), transform(i, j + 1, A), Math.min(A[i][j], A[i][j + 1])));
                }
                if (i + 1 < A.length) {
                    list.add(new Edge(transform(i, j, A), transform(i + 1, j, A), Math.min(A[i][j], A[i + 1][j])));
                }
            }
        }
        
        // 按边权从大到小排序
        list.sort((e1, e2) -> -Integer.compare(e1.len, e2.len));
        UnionFind uf = new UnionFind(A.length * A[0].length);
        for (Edge e : list) {
            uf.union(e.x, e.y);
            if (uf.find(0) == uf.find(transform(A.length - 1, A[0].length - 1, A))) {
                return e.len;
            }
        }
        
        return -1;
    }
    
    private int transform(int x, int y, int[][] A) {
        return x * A[0].length + y;
    }
}

时空复杂度一样。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值