题目描述
有一只跳蚤的家在数轴上的位置 x
处。请你帮助它从位置 0
出发,到达它的家。
跳蚤跳跃的规则如下:
- 它可以往前跳恰好
a
个位置(即往右跳)。 - 它可以往后跳恰好
b
个位置(即往左跳)。 - 它不能连续往后跳
2
次。 - 它不能跳到任何
forbidden
数组中的位置。
跳蚤可以往前跳超过它的家的位置,但是它不能跳到负整数的位置。
给你一个整数数组 forbidden
,其中 forbidden[i]
是跳蚤不能跳到的位置,同时给你整数 a
, b
和 x
,请你返回跳蚤到家的最少跳跃次数。如果没有恰好到达 x
的可行方案,请你返回 -1
。
解题思路
关键点:
- 最短路径;
- 无权图,或者说边权值为 1。
显然可以通过广度优先(BFS)来求解。由于搜索空间没有限制(具体说是上界),这造成如果算法搜索到家结点可以及时跳出广度的循环,而如果题目无解(return -1
),则会陷入上层的无效搜索空间进而导致死循环。故本体的关键先生为确定搜索空间的上界。这里题解作者给出了border = max(*max_element(forbidden.begin(), forbidden.end() + a + b, x + b)
的证明。但是其只证明了border
作为上界的合法性,并未推导border
是上确界的唯一性。
个人理解,搜索空间的上界至少满足> *max_element(forbidden.begin(), forbidden.end() + b
,保证后退一步后仍处于安全区,即后退后的位置大于forbidden[]
的最大值。进一步具体化确定上界的目的,即超过上界的点经过后退使其停留在安全区内。 倘若上界取*max_element(forbidden.begin(), forbidden.end() + b
,这意味着安全区只剩一个点,显然不是我们想要的结果。为保证足够的安全区空间,同时尽可能的压低上界以保证算法性能,故允许在安全区下界的基础上前进一步作为搜索空间上界,即*max_element(forbidden.begin(), forbidden.end() + a + b
,再考虑其他情况下后,最终取max()
。
代码实现
思考广度优先的去重,本题结点的访问有两种方式:1、正向(前几);2、反向(后退)。可能存在,反向访问A后,需要继续后退,但题目规则不允许连续后退,则需要再次正向访问A后后退,故使用二维数组。
class Solution {
public:
int minimumJumps(vector<int>& forbidden, int a, int b, int x) {
//计算上界
int f = 0;
for(int i : forbidden){
f = max(f, i);
}
int border = max(f + a + b, x + b);
//创建并初始化visited[][],二维数组
vector<vector<bool>> visited(border + 1,vector<bool>(2));
for(int i : forbidden){
visited[i][0] = true;
visited[i][1] = true;
}
//广度优先BFS
queue<tuple<int, int, int>> que;
que.push({0, 0, 0});
visited[0][0] = true;
while(!que.empty()){
auto [point, method, distance] = que.front();
que.pop();
if(point == x) return distance;
if(method == 1){
int newPos = point + a;
if(newPos <= border && !visited[newPos][0]){
que.push({newPos, 0, distance + 1});
visited[newPos][0] = true;
}
}else if(method == 0){
visited[point][0] = true;
int newPos = point + a;
if(newPos <= border && !visited[newPos][0]){
que.push({newPos, 0, distance + 1});
visited[newPos][1] = true;
}
newPos = point - b;
if(newPos > 0 && !visited[newPos][1]){
que.push({newPos, 1, distance + 1});
visited[newPos][1] = true;
}
}
}
return -1;
}
};
运行结果:
捋顺逻辑后,发现可以对visited[]
优化,正向访问(前进到达)结点必须标记(因为正向访问时结点下一步遍历不存在限制,所有可能分支都可以得到执行,没有重复访问的必要),反向访问则不标记(存在不能连续反向访问,存在需要再次访问此结点的可能性),节省了初始化visited[][]
的时间,大幅提高性能。
class Solution {
public:
int minimumJumps(vector<int>& forbidden, int a, int b, int x) {
//计算上界
int f = 0;
for(int i : forbidden){
f = max(f, i);
}
int border = max(f + a + b, x + b);
//创建并初始化visited[],一维数组
vector<bool> visited(border + 1);
for(int i : forbidden){
visited[i] = true;
}
//广度优先BFS
queue<tuple<int, int, int>> que;
que.push({0, 0, 0});
visited[0] = true;
while(!que.empty()){
auto [point, method, distance] = que.front();
que.pop();
if(point == x) return distance;
if(method == 1){
int newPos = point + a;
if(newPos <= border && !visited[newPos]){
que.push({newPos, 0, distance + 1});
visited[newPos] = true;
}
}else if(method == 0){
int newPos = point + a;
if(newPos <= border && !visited[newPos]){
que.push({newPos, 0, distance + 1});
visited[newPos] = true;
}
//反向访问,不标记!
newPos = point - b;
if(newPos > 0 && !visited[newPos]){
que.push({newPos, 1, distance + 1});
}
}
}
return -1;
}
};
运行结果: