【上界确定+BFS】1654. 到家的最少跳跃次数

题目描述

有一只跳蚤的家在数轴上的位置 x 处。请你帮助它从位置 0 出发,到达它的家。

跳蚤跳跃的规则如下:

  1. 它可以往前跳恰好 a 个位置(即往右跳)。
  2. 它可以往后跳恰好 b 个位置(即往左跳)。
  3. 它不能连续往后跳 2 次。
  4. 它不能跳到任何 forbidden 数组中的位置。

跳蚤可以往前跳超过它的家的位置,但是它不能跳到负整数的位置。

给你一个整数数组 forbidden ,其中 forbidden[i] 是跳蚤不能跳到的位置,同时给你整数 abx ,请你返回跳蚤到家的最少跳跃次数。如果没有恰好到达 x 的可行方案,请你返回 -1

解题思路

关键点:

  1. 最短路径;
  2. 无权图,或者说边权值为 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;
    }
};

运行结果:
result

捋顺逻辑后,发现可以对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;
    }
};

运行结果:
优化后运行结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值