LeetCode周賽#212
1631. 最小體力消耗路徑 #並查集 #最短路徑
題目連結
題意
給定一二維 rows x columns
的地圖 heights
,其中 heights[row][col]
表示格子 \((row, col)\) 的高度。一開始你在最左上角的格子 \((0, 0)\) ,且你希望去最右下角的格子 \((rows-1, columns-1)\) (下標從 0 開始)。每次可以往 上,下,左,右 四個方向之一移動,你想要找到耗費 體力 最小的一條路徑。
一條路徑耗費的 體力值 是路徑上相鄰格子之間 高度差絕對值 的 最大值 決定的。
分析
法一:Dijkstra求單源最短路徑。從起點出發,四個方向,鬆弛其鄰接的邊,將能夠鬆弛的頂點加入堆。其中頂點\((x,y)\)
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;
}
};