你准备参加一场远足活动。给你一个二维 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算法】
- 克鲁斯卡尔 (Kruskal) 算法,是用来求加权连通图的最小生成树的算法 。
- 基本思想 :按照权值从小到大的顺序选择 n-1 条边,并保证这 n-1 条边不构成回路
- 具体做法 :首先构造一个只含 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;
}
}