1. 程式人生 > 實用技巧 >LeetCode周賽#212

LeetCode周賽#212

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;
    }
};