leetcode-778. 水位上升的泳池中游泳之DFS+二分法与并查集

这篇博客介绍了如何利用图论的并查集方法和二分搜索来解决寻找矩阵中从左上角到右下角最小体力消耗路径的问题。对比了暴力DFS解法和优化后的解决方案,强调了剪枝在提高效率方面的重要性。并提供了详细的代码实现。

在这里插入图片描述这道题的前一天的每日一题”1631. 最小体力消耗路径“也是相同的解法,只是在条件上有所不同,甚至这道hard比1631的medium还简单一些!
对题目进行抽象,题目要求的从左上角到右下角其实可以抽象为寻找从左上角顶点到右下角的连通路径!很显然这个连通路径可以有多种,题目需要的是所经过的顶点的最大值最小的那条路径!
所以采用图论的方法,将每个每个坐标方格抽象为图中的顶点,将相邻两点间数值大的那个点的值作为边权值,对所有边依旧边权值从小到大排序。对这些边进行并查集Union连接,直到发现连接完当前边后,左上角顶点的parent和右下角顶点的parent相同(说明连通了),这条边的权值就是答案!

并查集代码

class Solution {
public:
    static bool cmp(vector<int>& a, vector<int>& b){
        return a[0]<b[0];
    }

    int Find(vector<int>& parent, int x){
        if(x!=parent[x])
            parent[x] = Find(parent, parent[x]);
        return parent[x];
    }

    void Union(vector<int>& parent, int x, int y){
        int x_root = Find(parent, x);
        int y_root = Find(parent, y);
        if(x_root!=y_root)
            parent[x_root] = y_root;
    }

    int swimInWater(vector<vector<int>>& grid) {
        vector<vector<int>> graph;
        int N = grid.size();
        // 建图
        for(int i=0;i<N;i++){
            for(int j=1;j<N;j++){
                vector<int> edge(3,0);
                edge[1] = grid[i][j-1];
                edge[2] = grid[i][j];
                // 相邻两点间数值大的那个点的值作为边权值
                edge[0] = max(edge[1],edge[2]); 
                graph.push_back(edge);
            }
        }
        for(int i=0;i<N;i++){
            for(int j=1;j<N;j++){
                vector<int> edge(3,0);
                edge[1] = grid[j-1][i];
                edge[2] = grid[j][i];
        
                edge[0] = max(edge[1],edge[2]);
                graph.push_back(edge);
            }
        }
        // 对所有边依据边权排序
        sort(graph.begin(),graph.end(),cmp);
        vector<int> parent(N*N, 0);
        for(int i=0;i<parent.size();i++)
            parent[i] = i;
        
        for(int i=0;i<graph.size();i++){ 
            Union(parent, graph[i][1], graph[i][2]);
            if(Find(parent,grid[0][0]) == Find(parent,grid[N-1][N-1]))
                return graph[i][0];
        }
        return 0;
    }
};

其实这题最容易想到的是DFS爆破!但是不剪枝的DFS会超时

DFS爆破代码

class Solution {
public:
    int N;
    int res = INT_MAX;
    int swimInWater(vector<vector<int>>& grid) {
        N = grid.size();
        vector<vector<int>> visited(N,vector<int>(N,0));
        int m = INT_MIN;
        DFS(grid,visited,0,0,m);
        return res; 
    }

    void DFS(vector<vector<int>>& grid, vector<vector<int>>& visited, int i, int j,int m){
        if(i<0||j>=N||i>=N||j<0||visited[i][j] == 1)
            return;
        visited[i][j] = 1;
        m = max(m,grid[i][j]);
        if(i == N-1 && j == N-1){
            visited[i][j] = 0;
            res = min(m,res);
            return;
        }

        DFS(grid,visited,i-1,j,m);
        DFS(grid,visited,i+1,j,m);
        DFS(grid,visited,i,j-1,m);
        DFS(grid,visited,i,j+1,m);
        visited[i][j] = 0;
    }
};

这里我们采用二分法来剪枝!首先我们知道矩阵中值的范围在0到N×N-1之间,我们需要找的答案在这个范围内!我们可以对这个答案进行二分查找,对于中值mid,判断矩阵中是否可以在所经过所有元素的值都小于mid的情况下,从左上角走到右上角,要是可以说明实际的答案还要小于mid,要是不行,说明实际的答案是大于mid的!然后对范围进行缩小,再循环执行上面的步骤,直到找到答案

DFS+二分法

class Solution {
public:
    int N;
    int res = INT_MAX;
    bool flag = false;
    int mid = 0;
    int swimInWater(vector<vector<int>>& grid) {
        N = grid.size();
        vector<vector<int>> visited(N,vector<int>(N,0));
        int m = INT_MIN;
        int left = 0;
        int right = N*N-1;
        while(left<right){
            mid = (left+right)/2;
            DFS(grid,visited,0,0,m);
            if(flag == true)
                right = mid;
            else
                left = mid+1;
            flag = false;
            visited = vector<vector<int>>(N,vector<int>(N,0));
        }
        return left; 
    }

    void DFS(vector<vector<int>>& grid, vector<vector<int>>& visited, int i, int j,int m){
        if(i<0||j>=N||i>=N||j<0||visited[i][j] == 1||grid[i][j]>mid||flag == true)
            return;
        visited[i][j] = 1;
		//下面千万不要加上被注释掉的visited[i][j] = 0!这里是关键!
		//为什么不加呢?因为要是加上的话,那后续遍历又会再走一次之前走过的但是没有用的路!
        if(i == N-1 && j == N-1){
            // visited[i][j] = 0;
            flag = true;
            return;
        }

        DFS(grid,visited,i-1,j,m);
        DFS(grid,visited,i+1,j,m);
        DFS(grid,visited,i,j-1,m);
        DFS(grid,visited,i,j+1,m);
        // visited[i][j] = 0;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值