题目
你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights ,其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左上角的格子 (0, 0) ,且你希望去最右下角的格子 (rows-1, columns-1) (注意下标从 0 开始编号)。你每次可以往 上,下,左,右 四个方向之一移动,你想要找到耗费 体力 最小的一条路径。
一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。
请你返回从左上角走到右下角的最小 体力消耗值 。
示例 1:
输入:heights = [[1,2,2],[3,8,2],[5,3,5]]
输出:2
解释:路径 [1,3,5,3,5]
连续格子的差值绝对值最大为 2 。 这条路径比路径 [1,2,2,2,5] 更优,因为另一条路径差值最大值为 3 。示例 2:
输入:heights = [[1,2,3],[3,8,4],[5,3,5]]
输出:1
解释:路径 [1,2,3,4,5]
的相邻格子差值绝对值最大为 1 ,比路径 [1,3,5,3,5] 更优。示例 3:
输入:heights =
[[1,2,1,1,1],[1,2,1,2,1],[1,2,1,2,1],[1,2,1,2,1],[1,1,1,2,1]]
输出:0
解释:上图所示路径不需要消耗任何体力。
提示:
rows == heights.length columns == heights[i].length 1 <= rows, columns
<= 100 1 <= heights[i][j] <= 106
解答
思路
- 首先可以将整个放个看成一个带权重的无向图,每个点之间的绝对值差就是图中像相邻点的边的权重
- 将每个边进行排序后,可得到边的升序序列
- 将这些边一次从小到大加入并查集中,知道,左上角与右下角连通
代码
// 并查集模板
class UF
{
public:
vector<int> uf;
vector<int> rank;
vector<int> size;
int count;
UF(int number):uf(vector<int>(number)),rank(vector<int>(number)),size(number,1),count(number)
{
iota(uf.begin(), uf.end(),0);
iota(rank.begin(),rank.end(),0);
}
int Find(int index)
{
return index==uf[index]?index:uf[index]=Find(uf[index]);
}
bool Union(int index1,int index2)
{
int find1 = Find(index1);
int find2 = Find(index2);
if (find1!=find2)
{
if(rank[find1]<rank[find2]) swap(find1,find2);
uf[find2] = find1;
if(rank[find1]==rank[find2]) rank[find1]+=1;
size[find1]+=size[find2];
--count;
return true;
}
else return false;
}
bool Connect(int index1,int index2)
{
return Find(index1)==Find(index2);
}
};
class Solution {
public:
int minimumEffortPath(vector<vector<int>>& heights) {
int m = heights.size();
int n = heights[0].size();
vector<tuple<int,int,int>> edges;
for(int i=0;i<m;++i)
{
for(int j=0;j<n;++j)
{
int id = i*n+j;
if(i>0)
edges.emplace_back(id-n,id,abs(heights[i][j]-heights[i-1][j]));
if(j>0)
edges.emplace_back(id-1,id,abs(heights[i][j]-heights[i][j-1]));
}
}
sort(edges.begin(),edges.end(),[](const auto& e1,const auto&e2){
auto&& [x1,y1,v1] = e1;
auto&& [x2,y2,v2] = e2;
return v1<v2;
});
UF uf(m*n);
int ans =0;
for(const auto [x,y,v]:edges)
{
uf.Union(x,y);
if(uf.Connect(0,m*n-1))
{
ans = v;
break;
}
}
return ans;
}
};
要点
- iota函数:定义在 numeric 头文件中的 iota() 函数模板会用连续的 T 类型值填充序列。前两个参数是定义序列的正向迭代器,第三个参数是初始的 T 值。第三个指定的值会被保存到序列的第一个元素中。保存在第一个元素后的值是通过对前面的值运用自增运算符得到的。当然,这意味着 T 类型必须支持 operator++()。
简单来说: 就是将第三个参数作为初始值放入第一个参数中,之后通过无限自增1而填充整个容器 vector<tuple<int,int,int>> edges
的妙用,可以将三个int整型作为一个元组,并且放入vector中的一维edges.emplace_back(id-n,id,abs(heights[i][j]-heights[i-1][j]))
,tuple容器可以用emplace_back()
进行填充。