p1631.path-with-minimum-effort

https://leetcode-cn.com/problems/path-with-minimum-effort/
 
keywords: dfs, bfs, 并查集
 

分析:

首先进行模型抽象,将地图抽象为图论模型。
地图中每个格子抽象为一个节点;每个上下或左右相邻的节点之间对应一条无向边,边权值为两个格子高度差的绝对值。
需要找到一条从左上角到右下角的最短路径,其中路径的长度定义为该路径所有边边权值的最大值。
 

题解:

 
方法一:并查集
将所有边按照从小到大顺序排列,依次从中选择一条边,将边的两个节点Union。若加入该边后0号节点和最后一个节点在同一个集合中即存在一条从0号节点到最后一个节点的通路,则该边的权值即为所求。
 
c++代码:

#include <iostream>
#include <vector>
#include <tuple>
#include <algorithm>
#include <sstream>
using namespace std;

class UFSets{
    private:
    int maxSize;
    int setCount;//连通分量数
    vector<int> parent;

    public:
    UFSets(int size=10):maxSize(size){
        parent=vector<int>(size);
        for(int i=0;i<size;i++){
            parent[i]=-1;
        }
        setCount=size;
    }
    ~UFSets(){
        //delete parent;
    }

    int& operator[](int i){
        return parent[i];
    }

    int Find(int v){
    return parent[v]<0?v:Find(parent[v]);
};//查找v所在并查集的根
    bool Union(int v1,int v2){
    int r1=Find(v1),r2=Find(v2);

    if(r1==r2){//若两个结点在同一个并查集
        return false;
    }
    //不在一个并查集则合并
    if(parent[r1]>=parent[r2]){
        parent[r2]+=parent[r1];
        parent[r1]=r2;
    }
    else{
        parent[r1]+=parent[r2];
        parent[r2]=r1;
    }
    setCount--;
    return true;
};//将并查集r1和并查集r2合并

int size(){
    return setCount;
}

};


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(j<n-1){
                edges.push_back(tuple<int,int,int>(id,id+1,abs(heights[i][j]-heights[i][j+1])));
            }
            if(i<m-1){
                edges.push_back(tuple<int,int,int>(id,id+n,abs(heights[i][j]-heights[i+1][j])));
            }
        }
    }
    sort(edges.begin(),edges.end(),[](tuple<int,int,int> e1,tuple<int,int,int> e2){
        return get<2>(e1)<get<2>(e2);
    });

    UFSets g(m*n);
    for(auto &e : edges){
        g.Union(get<0>(e),get<1>(e));
        if(g.Find(0)==g.Find(m*n-1)){
            return get<2>(e);
        }
    }
}

int main(){
    vector<vector<int> >heights;
    
    string temp;
    while(getline(cin,temp)){
        istringstream in(temp);

        int t;
        vector<int> ts;
        while(in>>t){
            ts.push_back(t);
        }
        heights.push_back(ts);
    }

    cout<<minimumEffortPath(heights);

    return 0;

}

 
方法二:二分查找
找到一个最小x,使得存在一条从0号节点到最后一个节点的通路。
利用二分查找依次选择x;利用dfs或bfs查找通路。
 
c++代码:(转自leetcode)

class Solution {
private:
    static constexpr int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    
public:
    int minimumEffortPath(vector<vector<int>>& heights) {
        int m = heights.size();
        int n = heights[0].size();
        int left = 0, right = 999999, ans = 0;
        while (left <= right) {
            int mid = (left + right) / 2;
            queue<pair<int, int>> q;
            q.emplace(0, 0);
            vector<int> seen(m * n);
            seen[0] = 1;
            while (!q.empty()) {
                auto [x, y] = q.front();
                q.pop();
                for (int i = 0; i < 4; ++i) {
                    int nx = x + dirs[i][0];
                    int ny = y + dirs[i][1];
                    if (nx >= 0 && nx < m && ny >= 0 && ny < n && !seen[nx * n + ny] && abs(heights[x][y] - heights[nx][ny]) <= mid) {
                        q.emplace(nx, ny);
                        seen[nx * n + ny] = 1;
                    }
                }
            }
            if (seen[m * n - 1]) {
                ans = mid;
                right = mid - 1;
            }
            else {
                left = mid + 1;
            }
        }
        return ans;
    }
};

 

思考:

做题的时候第一反应是dfs或bfs,想暴力地找到所有路径进行权值比较,从正面突破。。。。
没有想到反向突破,即检查某个x是否符合要求。

posted @ 2021-01-30 20:37  圆圆的圈圈  阅读(89)  评论(0)    收藏  举报