leetcode 1631. 最小体力消耗路径 二分+BFS、并查集、Dijkstra算法

最小体力消耗路径

在这里插入图片描述
题目与水位上升的泳池中游泳类似

二分查找+BFS

首先,采用二分查找,确定一个体力值,再从左上角,进行BFS,查看能否到达右下角,如果不行,二分查找就往大的数字进行查找,如果可以,还要继续往小的数字进行查找,比如示例1,数字10肯定可以到达右下角,但不是最小的体力。

class Solution {
public:
    int dx[4] = { 0,0,-1,1 };
    int dy[4] = { -1,1,0,0 };
    int m, n;
    bool bfs(vector<vector<int>>& heights, vector<vector<int>> exist, int sub)//二分+BFS
    {
        exist[0][0] = 1;
        queue<pair<int, int>> q;
        q.emplace(0, 0);

        while (!q.empty())
        {
            auto [i, j] = q.front();
            q.pop();

            for (int k = 0; k < 4; ++k)//上下左右四个方向
            {
                int newi = i + dx[k];
                int newj = j + dy[k];
                if (newi >= 0 && newi < m && newj >= 0 && newj < n && !exist[newi][newj] && sub >= abs(heights[i][j] - heights[newi][newj]))
                {
                    exist[newi][newj] = 1;

                    if (newi == m - 1 && newj == n - 1)//到达右下角
                    {
                        return true;
                    }

                    q.emplace(newi, newj);
                }
            }
        }
        return false;
    }
    int minimumEffortPath(vector<vector<int>>& heights)
    {
        m = heights.size(), n = heights[0].size();
        vector<vector<int>> exist(m, vector<int>(n, 0));

        int begin = 0, end = 999999;//最大值,由题目给的边界值得出
        int result = 0;
        while (begin <= end)
        {
            int mid = (begin + end) >> 1;
            if (bfs(heights, exist, mid))
            {
                result = mid;
                end = mid - 1;
            }
            else
            {
                begin = mid + 1;
            }
        }

        return result;
    }
};

二分+BFS一样适合于水位上升的题目。

二分+并查集
依然采用二分查找,确定一个值,只不过BFS换成了并查集

并查集:开辟一个数组,存储每个结点的父节点,当二分查找的某一个值,大于某两个点的差值,就将将其中一个点作为另外一个点的父节点,最后,如果可以到达右下角,那么,左上角的父节点就是右下角,此时,二分查找的值就可能是体力最小值。

这里用到了二维转一维

//并查集
class DSU
{
public:
    DSU(int n)
        :parent(vector<int>(n, 0))
    {
        for (int i = 0; i < n; ++i)//父节点先初始化为自己
        {
            parent[i] = i;
        }
    }

    int Find(int pos)
    {
        if (parent[pos] != pos)
            parent[pos] = Find(parent[pos]);//赋值为祖宗结点,减少搜索次数
            //return Find(parent[pos])parent[pos]为父节点

        return parent[pos];
    }

    void Union(int i, int j)
    {
        parent[Find(i)] = Find(j);
    }

    bool check(int i, int j)
    {
        return Find(i) == Find(j);
    }
private:
    vector<int> parent;
};

class Solution {
public:
    int minimumEffortPath(vector<vector<int>>& heights)
    {
        int m = heights.size(), n = heights[0].size();
        int result = 0;
        int begin = 0, end = 999999;//这里每次都得重新连接一遍,所以用二分,跟水池上升的游泳的题目相比
        while (begin <= end)
        {
            int mid = (begin + end) >> 1;
            DSU dsu(m * n);//二维转一维
            for (int i = 0; i < m; ++i)
            {
                for (int j = 0; j < n; ++j)
                {
                    if (i + 1 < m && abs(heights[i + 1][j] - heights[i][j]) <= mid)//下标方格可以到达
                    {
                        dsu.Union(i * n + j, (i + 1) * n + j);
                    }

                    if (j + 1 < n && abs(heights[i][j + 1] - heights[i][j]) <= mid)//右边方格可以到达
                    {
                        dsu.Union(i * n + j, i * n + j + 1);
                    }
                }
            }
            if (dsu.check(0, m * n - 1))
            {
                result = mid;
                end = mid - 1;
            }
            else
            {
                begin = mid + 1;
            }
        }
        return result;
    }
};

在这里插入图片描述
相比水位上升的题目的并查集,这里的并查集并没有那么有趣,因为水位上升的题目的是采用从0遍历到最大值而不是二分查找,再对某一个值进行并查集。因为,由于水位上升的题目的数据是不重复的,所以可以采用哈希表记录每个值的位置,从0到最大值,只要在某个值,左上角和左下角已经连通,就是答案。比如示例一,分别使用哈希表的记录每个数字的位置,遍历水位,当水位为0时,没有可以连接的,但是水位为1时,可以连接0-》1,水位为2时,连接0-》2,水位为3时,连接1-》3,2-》3。如果这里采用二分的话,假如结果是10时,全部都被连通了,要往下查找更小的值的话,就要重新开辟parent数组。

如果,在最小体力消耗路径的题目依然采用遍历,而不是二分查找的话,虽然还是一个parent数组,当体力来到2,体力1可以连接的点已经连接好了,但是你还是避免不了两层循环遍历heights,查看哪里还可以连接,而不是像上面题目那样,直接哈希表确认2的位置,进行上下左右判断是否可以连接。

Dijkstra算法

开辟一个数组,记录源顶点(左上角)到达某一个点的最小体力
当来到一个新的顶点,消耗的体力比记载的小,就要存储起来,并且以这一个点为新起点,更新上下左右的体力值

//Dijkstra算法
class Solution {
public:
    int minimumEffortPath(vector<vector<int>>& heights)
    {
        int m = heights.size(), n = heights[0].size();
        int INF = INT_MAX / 2;
        vector<int> dist(m * n, INF);//记录从源顶点,到达某个顶点的最小体力消耗
        dist[0] = 0;//顶点为0

        queue<tuple<int, int, int>> q;
        q.emplace(0, 0, 0);//分别表示最小体力差值,坐标
        while (!q.empty())
        {
            auto [physical, i, j] = q.front();
            q.pop();

            if (dist[i * n + j] < physical)//已经被处理过里,并且可以用更少的体力到达该位置
                continue;

            if (j + 1 < n)
            {
                int nextPhysical = max(physical, abs(heights[i][j] - heights[i][j + 1]));//到达左边的方格需要的体力
                if (nextPhysical < dist[i * n + j + 1])
                {
                    dist[i * n + j + 1] = nextPhysical;
                    q.emplace(nextPhysical, i, j + 1);
                }
            }

            if (i + 1 < m)
            {
                int nextPhysical = max(physical, abs(heights[i][j] - heights[i + 1][j]));//到达下边的方格需要的体力
                if (nextPhysical < dist[(i + 1) * n + j])
                {
                    dist[(i + 1) * n + j] = nextPhysical;
                    q.emplace(nextPhysical, i + 1, j);
                }
            }

            if (i - 1 >= 0)
            {
                int nextPhysical = max(physical, abs(heights[i][j] - heights[i - 1][j]));//到达上边方格需要的体力
                if (nextPhysical < dist[(i - 1) * n + j])
                {
                    dist[(i - 1) * n + j] = nextPhysical;
                    q.emplace(nextPhysical, i - 1, j);
                }
            }

            if (j - 1 >= 0)
            {
                int nextPhysical = max(physical, abs(heights[i][j] - heights[i][j - 1]));//到达左边方格需要的体力
                if (nextPhysical < dist[i * n + j - 1])
                {
                    dist[i * n + j - 1] = nextPhysical;
                    q.emplace(nextPhysical, i, j - 1);
                }
            }
        }

        return dist[m * n - 1];
    }
};
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值