LeetCode之45. 跳跃游戏 II

17 篇文章 1 订阅

题目

给定一个非负整数数组,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

你的目标是使用最少的跳跃次数到达数组的最后一个位置。

示例:

输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
     从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

说明:
假设你总是可以到达数组的最后一个位置。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/jump-game-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

分析

多做dp题,趁早找到dp的感觉。

这道题第一次提交没有AC,用例通过了98%左右,最后俩用例超时了,在此记录一下当时的思路,以及贪心算法的正确打开方式。

树的最短路径

  • 数组的第一个元素为树的根节点
  • 每个节点都有自己的子节点列表childList,记录的是当前位置能够走到的valindex
  • 创建树,当前节点的childList存在最后一个元素cur.val + cur.index大于等于数组的最后一个元素index下一步已经能够走到数组的尾端)时,不再对此节点的childList继续创建子节点。
  • 创建完成后计算该树的根节点到叶子节点的最短路径即可。

树的最短路径BFS(加速版)

考虑到每次获取最终结果都要把树创建完,想了一种办法,不需要创建完成树即可获取到最短路径。

创建树的时候,采用BFS方式进行创建树。由于是BFS,即一层一层创建叶子节点,当创建的当前节点能够走到最后一个节点时,当前路径就是最短路径。
获取到最短路径后直接返回,不需要把整棵树创建完成后再返回。

PS:这里有个加速的小技巧,由于多个节点之间可能存在相同元素的childList
如[3,4,5], 3childList包含45,而4childList又包含5,则534中是重复的。

但是其实是没必要的,因为一个元素只需要遍历一次即可,记录当前已经能够走到的最大索引maxIndex,若计算到的当前childList的元素的Index小于maxIndex,则不记录,因为其他节点已经对它记录过了。

超时代码如下:

typedef struct Node{
    int val = 0;
    int index = 0;
    int step = 0;

    Node()
    {}
    Node(int val, int index, int step) : val(val), index(index), step(step)
    {}
}Node;


class Solution {
public:
    int jump(vector<int>& nums) {
        if (nums.size() < 2) {
            return 0;
        }

        Node root(nums[0], 0, 0);
        return Cal(root, nums);
    }

    int Cal(Node &root, vector<int> &nums) {
        queue<Node> queue;
        queue.push(root);
        int lastStep = 0; // 假设总是能到达最后位置则该值应该用不上
        int maxIndex = 0; // 防止重复数据入队

        int i;
        while (!queue.empty()) {
            Node &cur = queue.front();
            for (i = 1; i <= cur.val; ++i) {
                if (i + cur.index >= nums.size() - 1) {
                    return cur.step + 1;
                }

                int nextIndex = i + cur.index;
                if (nextIndex <= maxIndex) {
                    continue;
                }
                maxIndex = nextIndex;
                queue.push(Node(nums[i + cur.index], i + cur.index, cur.step + 1));
            }
            lastStep = cur.step + 1;
            queue.pop();
        }

        return lastStep;
    }
};

贪心算法(最终版)

一直以来都把DP和贪心算法搞混
贪心算法思想:把问题分解后每一步都选择最优的选择,最终解决问题的选择也是最优选择。

DP的思想比较容易理解:将一个大问题Q分解成若干个小问题q1,q2,q3…qn,然后逐步求解所有小问题。
最重要的是,以上一个小问题的结果作为下一个小问题的输入,逐步求解到qn,即是问题Q的最终解。

用贪心算法去理解这道题,可以理解为每走一步都要走最大的那一步。(不是很严谨)

  • 将数组分为若干步,第一步从第1个元素开始走。
  • 走完第一步后,遍历当前可选择的元素,记录下跨越下一步的最大索引,当超过这个索引后就是下一步了,否则在0到这个索引内都属于当前步。
  • 直到下一步的最大索引大于数组的尾端。

代码

class Solution {
public:
    int jump(vector<int>& nums) {
        if (nums.size() < 2) {
            return 0;
        }
        int step = 0;
        int splitNextStep = 0; // 下一步的分割点
        int maxDistance = nums[0]; // 每次都选取当前步内的最长距离作为下一步的分割点

        for (int i = 0; i < nums.size(); ++i) {
            maxDistance = std::max(nums[i] + i, maxDistance);
            if (maxDistance >= nums.size() - 1) {
                return step + 1;
            }
            if (splitNextStep == i) {
                // 达到了下一个步数的分割点
                splitNextStep = maxDistance;
                ++step;
            }
        }
        return step + 1;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值