网易互娱22.4.16笔试LCP 09. 最小跳跃次数

LCP 09. 最小跳跃次数
为了给刷题的同学一些奖励,力扣团队引入了一个弹簧游戏机。游戏机由 N 个特殊弹簧排成一排,编号为 0 到 N-1。初始有一个小球在编号 0 的弹簧处。若小球在编号为 i 的弹簧处,通过按动弹簧,可以选择把小球向右弹射 jump[i] 的距离,或者向左弹射到任意左侧弹簧的位置。也就是说,在编号为 i 弹簧处按动弹簧,小球可以弹向 0 到 i-1 中任意弹簧或者 i+jump[i] 的弹簧(若 i+jump[i]>=N ,则表示小球弹出了机器)。小球位于编号 0 处的弹簧时不能再向左弹。

为了获得奖励,你需要将小球弹出机器。请求出最少需要按动多少次弹簧,可以将小球从编号 0 弹簧弹出整个机器,即向右越过编号 N-1 的弹簧。

示例 1:

输入:jump = [2, 5, 1, 1, 1, 1]

输出:3

解释:小 Z 最少需要按动 3 次弹簧,小球依次到达的顺序为 0 -> 2 -> 1 -> 6,最终小球弹出了机器。

限制:

1 <= jump.length <= 10^6
1 <= jump[i] <= 10000

LCP 09. 最小跳跃次数
题解

下面纯复盘:

PS:准备的时候压根没刷BFS和DFS的图遍历算法,直接gg。看到题目的时候第一反应是和青蛙跳石头过河的题目有点相像,那道题做过,所以想着能不能用动态规划和深度遍历的方法做,动态规划的话这次的球可以往后跳,而且球可以跳出范围,所以不好确定dp数组的范围,也不知道怎么处理球往开头位置跳的情况。所以想了想深度遍历,其实就是单纯的将每一步可以到达的位置列出来,包括从这个位置往前跳的位置和往后跳的任意位置,选出下一个能跳更远的位置继续(就是说如果前面的位置能跳得更远那就选那个位置,不然就选后面的),情况是给的样例全过了,自己想的样例也过了,就是提交后一个不过,gg

PSSS:其实就是BFS套模板,为什么我不早点会。。。

题解

寻找最少按动次数,相当于到或者超过弹珠机范围的最短路径,两者是等价的

所以使用BFS,根据解答链接的说法,这是一种扩展问题,目的是求弹珠的覆盖(弹)范围,这个范围随着弹珠不断跳动而扩展,就像一个点扩展到一个面,所以用BFS求覆盖面超过范围的最少按动次数

确定算法后就是BFS的框架了,queue记录覆盖范围,每个点是弹珠能弹的位置和对应的按动次数

初值就是0,0

因为一个位置被扩展过了,后面就不用再扩展这个位置了,因为后面扩展到这个位置按动次数肯定会更多,我们需要少的,而且会重复访问

这题扩展分为两个方向:
1、向前扩展:只有idx + jump[idx];超出范围那就可以直接返回了,记录访问记录
2、向后扩展:重点,按照题意应该是从0开始到该位置idx之间的点需要扩展,问题是这样的话最坏情况下是O(n^2 )(因为假设队列的顶点位置是10,9,8,那么就需要遍历9,8,7个顶点,以此类推n^2),最坏情况会超时,因此这里巧妙的地方就在于设置了preidx,preidx都是已扩展过的顶点,这样循环就变成了判断,只需要执行一次,从 n^ 2 变成n。为什么能这么设置呢?那是因为题目说到每次向后跳是都可以覆盖之前所有的位置,因此相当于之前的位置每个点扩展一遍就够了,后面的位置想要往前跳,就只需要减掉以扩展部分即可

建议用这个版本,这个版本属于是BFS模板:重点是while(pre < cur)这里一定要这么写

不写抑或是写成for循环的话都会超时,解决办法就是记录前面数据上一个的遍历的最后位置

class Solution {
public:
    int minJump(vector<int>& jump) {
        queue<int> que;
        unordered_set<int> hash;
        que.push(0);
        hash.insert(0);
        int res = 0;
        int pre = 0;
        while(!que.empty()){
            int size = que.size();
            res++;
            for(int i = 0;i < size;++i){
                int cur = que.front();
                que.pop();
                
                if(jump[cur] + cur >= jump.size()){
                    return res;
                }
                if(hash.find(jump[cur] + cur) == hash.end()){
                    hash.insert(jump[cur] + cur);
                    que.push(jump[cur] + cur);
                }
                while(pre < cur){
                    if(hash.find(pre) == hash.end()){
                        hash.insert(pre);
                        que.push(pre);
                    }
                    ++pre;
                }
            }
         }  
        return -1;
    }
};

这个版本是题解中的,和模板有点出入

class Solution {
public:
    int minJump(vector<int>& jump) {
        int n = jump.size();

        queue< pair<int, int> > q; // 当前位置 idx,按动次数 d
        // 初始值:编号为 0 的弹簧  按动次数 为0
        q.emplace(0, 0);

        // 某一个位置已经被扩展过了,就不需要被再次扩展:BFS最短路
        // 记录某个位置是否被扩展过:seen
        vector<bool> seen(n, false);
        seen[0] = true;


        int preidx = 1;
        // BFS
        while(!q.empty()) {
            auto [idx, d] = q.front();
            q.pop();

            // 向右扩展
            int next = idx + jump[idx];
            if(next > n - 1) {
                // 跳出弹簧
                // BFS 最短路
                return d + 1;
            }
            if(!seen[next]) {
                seen[next] = true;
                q.emplace(next, d + 1);
            }

            // 向左扩展:O(n^2)
            // 某一个位置及其之前所有位置都已经被扩展过,那么一定是最短路,不需要再次被扩展
            // preidx:记录某个位置及其之前位置均已被扩展,每次更新preidx 即可
            // for(int i = 0; i < idx; i++) {
            //     if(!seen[i]) {
            //         seen[i] = true;
            //         q.emplace(i, d + 1);
            //     }
            // }

            while(preidx < idx) {
                // 最多只会遍历一遍全部位置 :O(n)
                if(!seen[preidx]) {
                    seen[preidx] = true;
                    q.emplace(preidx, d + 1);
                }
                preidx++;
            }
        }

        // 遍历完整个队列,还未返回,说明无法到达
        return -1;

    }
};

PS:另外两道题是中序序列和后序序列建立树、再求出最长路径(这个过了80%,有20%超时),原因就是找根节点所在中序序列的位置我直接遍历整个数组,大概是这个原因正确做法是在一开始用一个map记录中序序列中所有值的下标索引

这里的重点是先建立右子树再建立左子树,这样做的原因是用于标定根节点的是后序序列的最后一个结点,因为后序是左右中,所以根节点一开始是最后一个值,然后不断往前都是右子树树根,遍历完右子树就是左子树。这样还有一个好处就是不需要标定后序序列的左右子树范围

举个例子,中序:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3],根节点位置在postorder是4,值是3,然后是3,值是20,20是右子树15 20 7的根节点,然后是2,值是7,然后是1,值是15
图是这样的:
在这里插入图片描述

class Solution {
    int post_idx;
    unordered_map<int, int> idx_map;
public:
    TreeNode* helper(int in_left, int in_right, vector<int>& inorder, vector<int>& postorder){
        // 如果这里没有节点构造二叉树了,就结束
        if (in_left > in_right) {
            return nullptr;
        }

        // 选择 post_idx 位置的元素作为当前子树根节点
        int root_val = postorder[post_idx];
        TreeNode* root = new TreeNode(root_val);

        // 根据 root 所在位置分成左右两棵子树
        int index = idx_map[root_val];

        // 下标减一
        post_idx--;
        // 构造右子树
        root->right = helper(index + 1, in_right, inorder, postorder);
        // 构造左子树
        root->left = helper(in_left, index - 1, inorder, postorder);
        return root;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        // 从后序遍历的最后一个元素开始
        post_idx = (int)postorder.size() - 1;

        // 建立(元素,下标)键值对的哈希表
        int idx = 0;
        for (auto& val : inorder) {
            idx_map[val] = idx++;
        }
        return helper(0, (int)inorder.size() - 1, inorder, postorder);
    }
};

附上先序和中序构建二叉树:
先序和中序

class Solution {
public:
    int pre_idx;
    unordered_map<int, int> idx_map;
    TreeNode* helper(int in_left, int in_right, vector<int>& inorder, vector<int>& preorder){
        // 如果这里没有节点构造二叉树了,就结束
        if (in_left > in_right) {
            return nullptr;
        }

        // 选择 pre_idx 位置的元素作为当前子树根节点
        int root_val = preorder[pre_idx];
        TreeNode* root = new TreeNode(root_val);

        // 根据 root 所在位置分成左右两棵子树
        int index = idx_map[root_val];

        // 下标加一
        pre_idx++;
        // 构造左子树
        root->left = helper(in_left, index - 1, inorder, preorder);
        // 构造右子树
        root->right = helper(index + 1, in_right, inorder, preorder);
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        pre_idx = 0;
        int idx = 0;
        for(auto &i:inorder){
            idx_map[i] = idx++;
        } 
        return helper(0,(int)inorder.size() - 1,inorder,preorder);
    }
};

第二题就是一个LRU的变种,PS:比原题简单多了
附上原题链接LRU

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值