2023.12.11每日一题 最小体力消耗路径【!!!!】

1631. 最小体力消耗路径

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

一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值最大值 决定的。

请你返回从左上角走到右下角的最小 体力消耗值

思路:

注:

当此类路径选择问题 ,方向不是两个方向,而是上下左右四个方向,存在S型移动时,就用不了状态转移方程了

事实上,当题目允许往任意方向移动时,考察的往往就不是 DP 了,而是图论。

从本质上说,DP 问题是一类特殊的图论问题。

此类看似 DP,实则图论的问题,通常是最小生成树或者最短路问题。

【如何建图】

1.因为相邻格子之间可以任意移动,所以相邻的格子之间存在一条无向边

2.边的权重 即为 相邻格子的高度差的绝对值

题目要我们求的就是从起点到终点,经过路径中的的「最小权重」最大的值是多少

遍历所有的格子,将所有的边加入集合。

存储的格式为数组 [a,b,w] ,代表编号为 a的点和编号为 b的点之间的权重为 w。

对集合进行排序,按照 w进行从小到大排序**(Kruskal 算法思想)**

【Kruskal算法】
  1. 克鲁斯卡尔 (Kruskal) 算法,是用来求加权连通图的最小生成树的算法 。
  2. 基本思想 :按照权值从小到大的顺序选择 n-1 条边,并保证这 n-1 条边不构成回路
  3. 具体做法 :首先构造一个只含 n 个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止

使用Kruskal算法的思想,我们依次从排好序的侯选边集合中,取边出来对边进行处理 -》加入并查集

每次加入一条边,就使用 并查集 来查询 起点 和终点是否构成了回路

当第一次发现「起点」和「终点」联通时,说明我们「最短路径」的所有边都已经应用到并查集上了,而且由于我们的边是按照「从小到大」进行排序,因此最后一条添加的边就是「最短路径」上权重最大的边。

【并查集】

定义:

并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)

主要构成:
并查集主要由一个整型数组pre[ ]和两个函数find( )、join( )构成。
数组 pre[ ] 记录了每个点的前驱节点是谁,

函数 find(x) 用于查找指定节点 x 属于哪个集合,

函数 join(x,y) 用于合并两个节点 x 和 y 。

代码:

/**
 * @author wxx
 * @version 1.0
 * @create 2023/12/11 14:32
 * 1631 :最小体力消耗路径
 * Kruskal+并查集
 */
class Solution16 {
    int N=100001;
    //并查集
    int[] pre=new int[N];
    //图的边
    List<int[]> edges=new ArrayList<>();
    //行数和列数
    int row;
    int col;
    //计算序号
    public int getIndex(int i,int j){
        return i*col+j;
    }
    //并查集:查最上级
    public int find(int x){
        while (pre[x]!=x){
            x=pre[x];
        }
        return x;
    }
    //并查集:合并最上级
    public void join(int x,int y){
        int fdx=find(x);
        int fdy=find(y);
        if(fdx!=fdy){
            pre[fdx]=fdy;
        }
    }

    public int minimumEffortPath(int[][] heights) {
        //行数
        row=heights.length;
        //列数
        col=heights[0].length;
        //初始化并查集
        for(int i=1;i<N;i++){
            pre[i]=i;
        }
        //初始化边
        for(int i=0;i<row;i++){
            for(int j=0;j<col;j++){
                int a=getIndex(i,j);
                if(j!=col-1){
                    int b=getIndex(i,j+1);
                    edges.add(new int[]{a,b,Math.abs(heights[i][j]-heights[i][j+1])});
                }
                if(i!=row-1){
                    int b=getIndex(i+1,j);
                    edges.add(new int[]{a,b,Math.abs(heights[i][j]-heights[i+1][j])});
                }
            }
        }
        //按照权重排序
        Collections.sort(edges,(a,b) -> a[2]-b[2]);

        //Kruskal算法
        //从权重小的边开始添加,借助并查集判断是否联通
        int start=getIndex(0,0);
        int end=getIndex(row-1,col-1);
        for(int[] edge:edges){
            int a=edge[0];
            int b=edge[1];
            join(a,b);
            if(find(start)==find(end)){
                return edge[2];
            }
        }
        return 0;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值