来源:力扣(LeetCode)
描述:
有一只跳蚤的家在数轴上的位置 x
处。请你帮助它从位置 0
出发,到达它的家。
跳蚤跳跃的规则如下:
a
个位置(即往右跳)。b
个位置(即往左跳)。2
次。forbidden
数组中的位置。跳蚤可以往前跳 超过 它的家的位置,但是它 不能跳到负整数 的位置。
给你一个整数数组 forbidden
,其中 forbidden[i]
是跳蚤不能跳到的位置,同时给你整数 a
, b
和 x
,请你返回跳蚤到家的最少跳跃次数。如果没有恰好到达 x
的可行方案,请你返回 -1
。
示例 1:
输入:forbidden = [14,4,18,1,15], a = 3, b = 15, x = 9
输出:3
解释:往前跳 3 次(0 -> 3 -> 6 -> 9),跳蚤就到家了。
示例 2:
输入:forbidden = [8,3,16,6,12,20], a = 15, b = 13, x = 11
输出:-1
示例 3:
输入:forbidden = [1,6,2,14,5,17,4], a = 16, b = 9, x = 7
输出:2
解释:往前跳一次(0 -> 16),然后往回跳一次(16 -> 7),跳蚤就到家了。
提示:
方法:广度优先搜索
思路
求最短路径一般需要用广度优先搜索,但是此题中的图是个无限图,如果不限制搜索的范围,无法处理无解的情况。因此,解决此题的关键是找出搜索的范围,其中下限已经由题目给出,不能跳到负整数的位置,我们还需要找出搜索的上限,下面分情况讨论:
这三个区域合起来组成了数轴上大于等于 0 的所有部分,注意 x 可能位于禁止区或者安全区,但不会是 forbidden 数组中的元素。假设某个步数最少的路径中,点 C 是第一个进入界外区(前进进入)的点,而点 H 是第一个离开界外区(后退离开)的点。因为 x 只可能位于禁止区或者安全区,因此如果这条路径存在点 C,那么必然存在点 H。如下图,横坐标为步数,纵坐标为与原点的距离。箭头朝右上表示前进,箭头朝右下表示后退。
接下来,我们通过交换线段 BC 和线段 GH ,并保持其他线段的的方向不变,来使得点 C 不再位于界外区。如下图,线段 BC’ 变为后退而线段 G’H 变为前进。
我们从以下几个方面论证这种交换的可行性:
通过这样的交换,我们使得一个有效路径第一个进入界外区的点,不再位于界外区。新的路径,第一个进入界外区的点,可能位于点 C 和点 H 之间,也可能位于点 HHH 之后,也可能不存在这样的点。总之,我们可以不停地寻找第一个进入界外区的点,然后经过上述的交换,使得最终的路径的所有点都位于禁止区和安全区。这样,我们就证明出,如果某个输入有解,那么至少有一条最短路径,它的所有点都处于上限 max(f + a + b, x) 之内。因此在这种情况下,上限为 max(max(forbidden) + a + b, x)。
综合以上三种情况,广度优先搜索的上限是 max(max(forbidden) + a, x) + b 。
在进行广度优先搜索时,除了需要注意到上下限,不能达到 forbidden 数组中的坐标,还需要注意到达每个坐标时,都会有前进到达还是后退到达两种状态。如果是前进到达时,下一步可以选择前进或者后退;如果是后退到达时,下一步只能选择前进。因此广度优先搜索的每个元素,需要保存三个信息,坐标,方向和步数。在代码中,我们用 1 表示前进, −1 表示后退,用哈希集合 visited 来记录已经达到过的位置和方向状态。在搜索的过程中,如果坐标第一次为 x,则返回当前步数。当队列为空时,表示 x 不可到达,返回 −1。
代码:
class Solution {
public:
int minimumJumps(vector<int>& forbidden, int a, int b, int x) {
queue<tuple<int, int, int>> q;
unordered_set<int> visited;
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> forbiddenSet(forbidden.begin(), forbidden.end());
while (!q.empty()) {
auto [position, direction, step] = q.front();
q.pop();
if (position == x) {
return step;
}
int nextPosition = position + a;
int nextDirection = 1;
if (lower <= nextPosition && nextPosition <= upper && !visited.count(nextPosition * nextDirection) && !forbiddenSet.count(nextPosition)) {
visited.emplace(nextPosition * nextDirection);
q.emplace(nextPosition, nextDirection, step + 1);
}
if (direction == 1) {
nextPosition = position - b;
nextDirection = -1;
if (lower <= nextPosition && nextPosition <= upper && !visited.count(nextPosition * nextDirection) && !forbiddenSet.count(nextPosition)) {
visited.emplace(nextPosition * nextDirection);
q.emplace(nextPosition, nextDirection, step + 1);
}
}
}
return -1;
}
};
时间 32ms 击败 44.76%使用 C++ 的用户
内存 18.11MB 击败 43.81%使用 C++ 的用户
复杂度分析
- 时间复杂度:O(max(max(forbidden)+a,x)+b)。表达式为广度优先搜索的位置上限,每个位置最多会被计算两次。
- 空间复杂度:O(max(max(forbidden)+a,x)+b)。表达式为广度优先搜索的位置上限,是队列和哈希集合的空间复杂度。
author:力扣官方题解