题目
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例:
输入: [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
,记录的是当前位置能够走到的val
和index
。 - 创建树,当前节点的
childList
存在最后一个元素或cur.val + cur.index
大于等于数组的最后一个元素index
(下一步已经能够走到数组的尾端)时,不再对此节点的childList
继续创建子节点。 - 创建完成后计算该树的根节点到叶子节点的最短路径即可。
树的最短路径BFS(加速版)
考虑到每次获取最终结果都要把树创建完,想了一种办法,不需要创建完成树即可获取到最短路径。
创建树的时候,采用BFS方式进行创建树。由于是BFS,即一层一层创建叶子节点,当创建的当前节点能够走到最后一个节点时,当前路径就是最短路径。
获取到最短路径后直接返回,不需要把整棵树创建完成后再返回。
PS:这里有个加速的小技巧,由于多个节点之间可能存在相同元素的childList
。
如[3,4,5], 3
的childList
包含4
和5
,而4
的childList
又包含5
,则5
在3
和4
中是重复的。
但是其实是没必要的,因为一个元素只需要遍历一次即可,记录当前已经能够走到的最大索引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;
}
};