1631. 最小体力消耗路径 #并查集 #最短路径

题目链接

题意

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

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

分析

法一:Dijkstra求单源最短路径。从起点出发,四个方向,松弛其邻接的边,将能够松弛的顶点加入堆。其中顶点\((x,y)\)松弛的条件,即为\(dis[x][y] > max(dis[u.i][u.j], diff)\),其中\(diff\)为\((x,y)\)到\((u.i,u.j)\)的边权。

typedef struct Node{
int i, j, dis;
bool operator < (const struct Node& others) const{
return dis > others.dis;
}
} node;
class Solution {
private:
int n, m;
int di[5] = {0, 1, 0, -1, 0};
int dj[5] = {0, 0, -1, 0, 1};
int dis[105][105];
public:
int minimumEffortPath(vector<vector<int>>& heights) {
n = heights.size();
m = heights[0].size();
memset(dis, 0x3f, sizeof(dis));
dis[0][0] = 0;
priority_queue<node> myque;
myque.push({0, 0, 0});
while(!myque.empty()){
node u = myque.top();
myque.pop();
if (u.dis != dis[u.i][u.j]) continue;
for (int t = 1; t <= 4; t++){
int x = u.i + di[t], y = u.j + dj[t];
if(x < 0 || x >= n || y < 0 || y >= m) continue;
int diff = abs(heights[u.i][u.j] - heights[x][y]);
if(dis[x][y] > max(dis[u.i][u.j], diff)){
dis[x][y] = max(dis[u.i][u.j], diff);
myque.push({x, y, dis[x][y]});
}
}
}
return dis[n - 1][m - 1];
}
};

法二:并查集。首先预处理,遍历矩阵中每个点,将每个点的四个方向的邻接点进行建边,边权为其两点间的差值。建立边集后,按边权从小到大排序边集,然后按此顺序依次将边中的点进行合并,直到起点与终点连通时,所对应的边的边权即为题目所求。

typedef struct Edge{
int u, v, w;
bool operator < (const struct Edge& others) const {
return w < others.w;
}
} edge;
class Solution {
private:
int fa[10005];
int n, m;
public:
int findSet(int x){
if(x != fa[x]) fa[x] = findSet(fa[x]);
return fa[x];
}
int minimumEffortPath(vector<vector<int>>& heights) {
n = heights.size(), m = heights[0].size();
vector<edge> E;
for (int i = 0; i < (n * m); i++) fa[i] = i;
for (int i = 0; i < n; i++){
for (int j = 0; j < m; j++){
int idx = i * m + j;
if(i - 1 >= 0) E.push_back({idx, idx - m, abs(heights[i][j] - heights[i - 1][j])});
if(j - 1 >= 0) E.push_back({idx, idx - 1, abs(heights[i][j] - heights[i][j - 1])});
}
}
sort(E.begin(), E.end());
for(int i = 0; i < E.size(); i++){
int pu = findSet(E[i].u), pv = findSet(E[i].v);
if(pu != pv) fa[pu] = pv;
int st = findSet(0), ed = findSet(n * m - 1);
if(st == ed) return E[i].w;
}
return 0; //避免图中只有一个顶点的情况
}
};

1632. 矩阵转换后的秩 #并查集

题目链接

题意

给你一个 m x n 的矩阵 matrix ,请你返回一个新的矩阵 answer ,其中 answer[row][col]matrix[row][col] 的秩。

每个元素的 是一个整数,表示这个元素相对于其他元素的大小关系,它按照如下规则计算:

  • 如果一个元素是它所在行和列的最小值,那么它的 为 \(1\)。

  • 如果两个元素 \(p\) 和 \(q\) 在 同一行 或者 同一列 ,那么:

    • 如果 \(p < q\) ,那么 \(rank(p) < rank(q)\)

    • 如果 \(p = q\) ,那么 \(rank(p) == rank(q)\)

      如果 \(p > q\) ,那么 \(rank(p) > rank(q)\) 秩 需要越 小 越好。

样例

分析

好多题解我都没看懂\(QAQ\),感谢@huanglin的代码帮我弄清这题的思路。

如果给定的矩阵并不存在一个相同的元素的话,那么只需要从小到大排序就能排出秩。但是题目中最主要的问题,在于同行、同列相同元素,会共享同一个秩。

如何解决?首先应将矩阵中的所有元素进行排序,然后按该顺序遍历每个顶点。将其对应的行、列中相同的元素合并在一个并查集中,然后再用当前的最大的秩号去更新这个并查集的代表元素(满足同行、同列的相同元素共享同一个秩)。

如果该顶点不存在行列相同的元素,那么就从当前行列的元素最大的秩号\(+1\)(满足 \(p < q\) ,那么 \(rank(p) < rank(q)\))。

class Solution {
private:
int n, m;
int fatherIdx[500 * 500 + 5], fatherVal[500 * 500 + 5] = { 0 };
public:
int findSet(int x) {
if (x != fatherIdx[x]) fatherIdx[x] = findSet(fatherIdx[x]);
return fatherIdx[x];
}
void Union(int x, int y) {
int px = findSet(x), py = findSet(y);
if (px != py) fatherIdx[px] = py;
}
vector<vector<int>> matrixRankTransform(vector<vector<int>>& matrix) {
n = matrix.size(); m = matrix[0].size();
vector< pair<int, int> > matrixVal; //将二维矩阵转化为一维,first值存原矩阵值,second值存其i*m+j编号
for (int i = 0; i < (n * m); i++)
matrixVal.push_back({ matrix[i / m][i % m], i });
sort(matrixVal.begin(), matrixVal.end()); //对一维压缩矩阵排序
vector<int> toRow(n, -1), toCol(m, -1); //toRow[i],代表第i行下【已知的】最大元素的列号;toCol反之
for (int i = 0; i < (n * m); i++) fatherIdx[i] = i; //初始化【行列并查集】(也就说,行列相同的元素连在一起) for (int pos = 0; pos < (n * m); pos++) { //从小到大,遍历一维压缩矩阵
int idx = matrixVal[pos].second; //获得第pos小的矩阵元素所属的原压缩位置
int i = idx / m, j = idx % m;
int val = 1;
if (toRow[i] != -1) { //第i行中最大元素的列号
int toIdx = i * m + toRow[i]; //压缩这个最大元素的状态
int toFatherIdx = findSet(toIdx);
int toFatherVal = fatherVal[toFatherIdx];
if (matrix[i][j] == matrix[i][toRow[i]]) { //如果第i行有两个相同的值,
Union(idx, toIdx);//合并集合扩大规模
val = max(val, toFatherVal);
}
else
val = max(val, toFatherVal + 1);
}
if (toCol[j] != -1) { //第j行中最大元素的行号
int toIdx = toCol[j] * m + j;
int toFatherIdx = findSet(toIdx);
int toFatherVal = fatherVal[toFatherIdx];
if (matrix[i][j] == matrix[toCol[j]][j]) {
Union(idx, toIdx);
val = max(val, toFatherVal);
}
else
val = max(val, toFatherVal + 1);
}
toRow[i] = j; toCol[j] = i;
int curFatherIdx = findSet(idx);
fatherVal[curFatherIdx] = val; //将这个最大的val赋给并查集的代表元素
}
vector<vector<int>> res(n, vector<int>(m));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
res[i][j] = fatherVal[findSet(i * m + j)]; //注意,一定要先找到并查集的代表元素
}
}
return res;
}
};

LeetCode周赛#212的更多相关文章

  1. 【Leetcode周赛】从contest-111开始。(一般是10个contest写一篇文章)

    Contest 111 (题号941-944)(2019年1月19日,补充题解,主要是943题) 链接:https://leetcode.com/contest/weekly-contest-111 ...

  2. 【LeetCode】212. Word Search II 解题报告(C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 前缀树 日期 题目地址:https://leetco ...

  3. 拼写单词[哈希表]----leetcode周赛150_1001

    题目描述: 给你一份『词汇表』(字符串数组) words 和一张『字母表』(字符串) chars. 假如你可以用 chars 中的『字母』(字符)拼写出 words 中的某个『单词』(字符串),那么我 ...

  4. 【Leetcode周赛】从contest-41开始。(一般是10个contest写一篇文章)

    Contest 41 ()(题号) Contest 42 ()(题号) Contest 43 ()(题号) Contest 44 (2018年12月6日,周四上午)(题号653—656) 链接:htt ...

  5. 【Leetcode周赛】从contest-51开始。(一般是10个contest写一篇文章)

    Contest 51 (2018年11月22日,周四早上)(题号681-684) 链接:https://leetcode.com/contest/leetcode-weekly-contest-51 ...

  6. 【Leetcode周赛】从contest-71开始。(一般是10个contest写一篇文章)

    Contest 71 () Contest 72 () Contest 73 (2019年1月30日模拟) 链接:https://leetcode.com/contest/weekly-contest ...

  7. 【Leetcode周赛】从contest-81开始。(一般是10个contest写一篇文章)

    Contest 81 (2018年11月8日,周四,凌晨) 链接:https://leetcode.com/contest/weekly-contest-81 比赛情况记录:结果:3/4, ranki ...

  8. 【Leetcode周赛】从contest-91开始。(一般是10个contest写一篇文章)

    Contest 91 (2018年10月24日,周三) 链接:https://leetcode.com/contest/weekly-contest-91/ 模拟比赛情况记录:第一题柠檬摊的那题6分钟 ...

  9. 【Leetcode周赛】从contest-121开始。(一般是10个contest写一篇文章)

    Contest 121 (题号981-984)(2019年1月27日) 链接:https://leetcode.com/contest/weekly-contest-121 总结:2019年2月22日 ...

随机推荐

  1. SpringApplication.run(xxx.class, args)背后的东东——整体脉络

    从spring到springmvc,再到springboot.springcloud,应用程序api开发调用方面都已经非常熟悉,但对spring背后的扩展机制:为何一个简单的main方法可以实现这么强 ...

  2. Luogu P5087 数学

    题意 给定一个长度为 \(n\) 的序列 \(a_i\),求出在这个序列中所有选出 \(k\) 个元素方案中元素的乘积之和. \(\texttt{Data Range:}1\leq n\leq 10^ ...

  3. TypeScript魔法堂:函数类型声明其实很复杂

    前言 江湖有传"动态类型一时爽,代码重构火葬场",由于动态类型语言在开发时不受数据类型的约束,因此非常适合在项目原型阶段和初期进行快速迭代开发使用,这意味着项目未来将通过重写而非重 ...

  4. Vue、Node全栈项目~面向小白的博客系统~

    个人博客系统 前言 ❝ 代码质量问题轻点喷(去年才学的前端),有啥建议欢迎联系我,联系方式见最下方,感谢! 页面有啥bug也可以反馈给我,感谢! 这是一套包含前后端代码的个人博客系统,欢迎各位提出建议 ...

  5. 关于python递归函数,这样写就对了

    大家好我是致力于让每个人都能够轻松学会编程的小梁,在这条路上任重道远,关注我,每天让您获取来自编程的乐趣. 关注公众号"轻松学编程".了解更多. 今天就给大家分享一下关于使用递归函 ...

  6. 简单谈谈Hilt——依赖注入框架

    今天继续Jetpack专题,相信不少的朋友都使用过Dagger,也放弃过Dagger,因为实在太难用了.所以官方也是为了让我们更好使用依赖注入框架,为我们封装了一个新的框架--Hilt,今天一起来看看 ...

  7. node.js+express框架 修改后自启【不需要再执行start】

    我们每次修改完后都需要重新启动下才能刷新,就很麻烦'nodemon'解决了这个问题. 这里直接进行全局安装 npm install -g nodemon 安装到本地 npm install nodem ...

  8. c#练习习题:while循环

    2006年培养学员80000人,每年增长25%,请问按此增长速度,到哪一年培训学员人数将达到20万人? int count = 80000; int year = 2006; while (count ...

  9. 【Python】如何结束退出 py 脚本

    需求 当你运行脚本,在判断条件满足时,就退出脚本,结束本次执行. 方法 使用 sys.exit(),直接退出程序,但是会引发一个 SystemExit 异常: 该方法包含一个 status 参数 sy ...

  10. 力扣 - 146. LRU缓存机制

    目录 题目 思路 代码 复杂度分析 题目 146. LRU缓存机制 思路 利用双链表和HashMap来解题 看到链表题目,我们可以使用头尾结点可以更好进行链表操作和边界判断等 还需要使用size变量来 ...