leetcode1631(最小体力消耗路径:最短路径算法)

你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights ,其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左上角的格子 (0, 0) ,且你希望去最右下角的格子 (rows-1, columns-1) (注意下标从 0 开始编号)。你每次可以往 上,下,左,右 四个方向之一移动,你想要找到耗费 体力 最小的一条路径。
一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。
请你返回从左上角走到右下角的最小 体力消耗值 。

例:
输入:heights = [[1,2,2],[3,8,2],[5,3,5]]输出:2
解释:路径 [1,3,5,3,5] 连续格子的差值绝对值最大为 2 。
这条路径比路径 [1,2,2,2,5] 更优,因为另一条路径差值最大值为 3 。

题解:最短路径算法

这道题本质上依然是求图中两点间的最短路径,图中边的权值是两顶点的权值之差,路径的长度是路径所包含的边的最大权值。

方法一:迪杰斯特拉算法

迪杰斯特拉算法是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,用一个数组int[ ]dist保存起点到其余各点的最短路径距离,采用贪心算法的策略,每次遍历到起点距离最近且未访问过的顶点的邻接节点并不断更新dist数组,直到扩展到终点为止。

class Solution {
    public int minimumEffortPath(int[][] heights) {
        int rowLen=heights[0].length;
        int colLen=heights.length;
        int[][]dir={{0,1},{0,-1},{1,0},{-1,0}};
        //路径排序,得到当前的最短路径
        PriorityQueue<int[]>queue=new PriorityQueue<int[]>
                (Comparator.comparingInt(o -> o[2]));
        //是否已经找到图中各顶点的最短路径
        boolean[]searched=new boolean[rowLen*colLen];
        queue.add(new int[]{0,0,0});
        //起点到图中其他各点的最短距离
        int[]dist=new int[rowLen*colLen];
        //初始化起点到其他各点的最短距离是无穷大
        Arrays.fill(dist,Integer.MAX_VALUE);
        //起点到起点的最短路径是0
        dist[0]=0;
        while (!queue.isEmpty()){
            int[]peek=queue.poll();
            int x=peek[0];
            int y=peek[1];
            int d=peek[2];
            if(searched[x*rowLen+y]){
                continue;
            }
            if(x==colLen-1&&y==rowLen-1){
                break;
            }
            searched[x*rowLen+y]=true;
            for(int i=0;i<4;i++){
                int xx=x+dir[i][0];
                int yy=y+dir[i][1];
                if(xx>=0&&xx<colLen&&yy>=0&&yy<rowLen&&Math.max(d,Math.abs(heights[x][y]-heights[xx][yy]))<dist[xx*rowLen+yy]){
                    dist[xx*rowLen+yy]=Math.max(d,Math.abs(heights[x][y]-heights[xx][yy]));
                    queue.add(new int[]{xx,yy,dist[xx*rowLen+yy]});
                }
            }
        }
        return dist[rowLen*colLen-1];
    }
}
方法二:并查集

将图中所有边的权值排序,每次从序列中取出当前最小边,将该最小边的两顶点表示成已连接,如此重复,直到起点与终点相连通,检验起点与终点是否连通时可以使用并查集。

class Solution {
    public int minimumEffortPath(int[][] heights) {
        int rowLen=heights[0].length;
        int colLen=heights.length;
        unionFind unionFind=new unionFind(colLen*rowLen);
        PriorityQueue<int[]>queue=new PriorityQueue<int[]>
                (Comparator.comparingInt(o -> o[2]));
        for(int i=0;i<colLen;i++){
            for(int j=0;j<rowLen;j++){
                if(i+1<colLen){
                    queue.add(new int[]{i*rowLen+j,(i+1)*rowLen+j,Math.abs(heights[i][j]-heights[i+1][j])});
                }
                if(j+1<rowLen){
                    queue.add(new int[]{i*rowLen+j,i*rowLen+j+1,Math.abs(heights[i][j]-heights[i][j+1])});
                }
            }
        }
        while (!queue.isEmpty()){
            int[]peek=queue.poll();
            unionFind.union(peek[0],peek[1]);
            if(unionFind.connected(0,colLen*rowLen-1)){
                return peek[2];
            }
        }
        return 0;
    }
}
class unionFind{
    int[]parent;
    public unionFind(int n){
        parent=new int[n];
        for(int i=0;i<n;i++){
            parent[i]=i;
        }
    }
    public void union(int a,int b){
        parent[find(a)]=find(b);
    }
    public int find(int a){
        while(a!=parent[a]){
            parent[a]=parent[parent[a]];
            a=parent[a];
        }
        return a;
    }
    public boolean connected(int a,int b){
        return find(a)==find(b);
    }
}

版权声明:本文为CY2333333原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。