1654. 到家的最少跳跃次数

文章目录

  • Tag
  • 题目来源
  • 题目解读
  • 解题思路
    • 实现细节
    • 实现代码
    • 复杂度分析
  • 写在最后

Tag

【广搜】【上限证明】【图论】

题目来源

1654. 到家的最少跳跃次数.
1654. 到家的最少跳跃次数_第1张图片

题目解读

找到从位置 0 跳跃到位置 x 的最小跳跃次数,跳跃规则如下:

  • 前进方向跳 a 个位置;
  • 后退方向跳 b 个位置;
  • 不能连续后退 2 次,也就是说,上一次是后退的,这次一定要前进;上次是前进的,这次可以前进也可以后退。
  • 有一个禁止数组 forbidden,表示禁止到达的位置。

如果没有合适的跳跃方案返回 -1

解题思路

本题求最小跳跃次数,是最短路问题。最短路问题一般都需要使用广度优先搜索,本题中的图是一个无限的图(因为有可能超过 x 位置后,还一直前进),因此需要限制搜索范围,否则无法处理无解的情况。

题目中已经给出了搜索的下限,不能跳到负整数位置,即下限 lower = 0,关于上限的寻找与证明,参考的是 newhar 的解答。

  • a >= b 即一次的前进距离大于等于后退距离时,当到达的位置大于 x + b 时,最多只能后退一次,也到不位置 x。这时不论怎么跳都不会到达位置 x 了。此时的搜索上限是 x + b
  • a < b 即一次的前进距离小于后退距离时,上限为 max(f + a +b, x) 具体证明请参考 newhar 的题解;
  • 最终的上限定为 upper = max(f + a + b, x + b)

找到了搜索的下限与上限之后,在该范围内进行广度优先搜索,当前位置到达 x 处,返回对应的步数;直到在范围内搜索无结果,最终返回 -1

实现细节

维护一个队列,存放三元组 posdirstep,分别表示 当前的位置上一个是前进还是后退(前进为 1,后退为 -1)、当前的步数

维护一个已经到达的位置和方向的集合,存放 位置*方向

forbidden 数组存入集合中,方便查找。

在进行 while 循环时,无论上次是前进还是后退的,这次都可以前进。上次是前进的话,还可以前进。

实现代码

class Solution {
public:
    int minimumJumps(vector<int>& forbidden, int a, int b, int x) {
        queue<tuple<int, int, int>> q;  // 三元组(位置,方向,步数)
        unordered_set<int> visited;     // 已经到达的位置和方向 1表示前进 -1表示后退 存放位置*方向
        q.emplace(0, 1, 0);
        visited.emplace(0);

        int lower = 0, upper = max(*max_element(forbidden.begin(), forbidden.end()) + a, x) + b;
        unordered_set<int> forbSet(forbidden.begin(), forbidden.end());
        while (!q.empty()) {
            auto [pos, dir, step] = q.front();
            q.pop();
            // 到达位置,返回步数
            if (pos == x) {
                return step;
            }

            // 不论上一步是前进还是后退的,这一步都可以前进
            int nextPos = pos + a;
            int nextDir = 1;
            if (lower <= nextPos && nextPos <= upper && !visited.count(nextPos * nextDir) && !forbSet.count(nextPos)) {
                visited.emplace(nextPos * nextDir);
                q.emplace(nextPos, nextDir, step + 1);
            }

            // 如果上一步是前进的,这一步还可以后退
            if (dir == 1) {
                nextPos = pos - b;
                nextDir = -1;
                if (lower <= nextPos && nextPos <= upper && !visited.count(nextPos * nextDir) && !forbSet.count(nextPos)) {
                    visited.emplace(nextPos * nextDir);
                    q.emplace(nextPos, nextDir, step + 1);
                }
            }
        }
        // 到达搜索上限后,仍然没有找到合适的跳跃方案,返回 -1
        return -1;
    }
};

复杂度分析

时间复杂度为搜索上限: O ( m a x ( m a x ( f o r b i d d e n ) + a , x ) + b ) O(max(max(forbidden) + a, x) + b) O(max(max(forbidden)+a,x)+b)

空间复杂度为队列大小: O ( m a x ( m a x ( f o r b i d d e n ) + a , x ) + b ) O(max(max(forbidden) + a, x) + b) O(max(max(forbidden)+a,x)+b)

写在最后

以上就是本篇文章的内容了,感谢您的阅读。

如果感到有所收获的话可以给博主点一个 哦。

如果文章内容有任何错误或者您对文章有任何疑问,欢迎私信博主或者在评论区指出。

你可能感兴趣的:(LeetCode每日一题,c++,算法)