算法设计与分析第七周作业——LeetCode之Jump Game
本周学习了贪心算法(也叫贪婪算法),所以就选择了一道与贪心算法有关的题目:Jump Game
题目详情
- 题目大意:给出一组数,假设你在数组的开始处(即下标为0处),你所在的下标对应的数组的元素是你能从该处跳跃的最大距离,问能否到达数组的尾部。
- 样例说明:
- 给出的数组为[2, 3, 1, 1, 4],此时我们可以模拟跳跃,从【0】跳1步到【1】,再从【1】跳3步到【4】(当然还有其他的方法可以到达)。故该输入可以达到终点。
- 给出的数组为[3, 2, 1, 0, 4] ,此时模拟跳跃过程,从【0】跳1步到【1】,再从【1】跳1步到【2】,从【2】跳1步到【3】,就不能前进了,经模拟其他的方法也不能到达终点。故该输入不能到达终点。
题目分析及算法设计
我们可以把题目想象成这样一个情景:数组的每个数表示在当前位置能够获得的能量,而在摄取这些能量之前要把之前剩余的能量都耗光,而跳跃一次能到达下一个地点,同时也会消耗掉一单位的能量。这是一个贪心算法的情景,因为我们总是想着靠当前位置获取的能量来尝试能够跳跃到最远的距离,而如果这个最远的距离超过当前位置与目的地之间的距离的话,就说明我们是可以到达的。
算法如下:
- 维护一个变量canReachMost来表示从当前位置能够到达的最远的距离,canReachMost初始化为0;
- 遍历数组,判断当前位置与能够到达的位置canReachMost之间的关系以及canReachMost与终点的关系,如果当前位置大于canReachMost,说明已经不能到达当前位置了,肯定也不能到达终点了,退出循环,而如果canReachMost大于或者等于终点的话,说明可以到达的,这时也退出循环;
- 如果当前位置和使用当前位置的能量能够到达的位置比之前记录的能到达的最远位置更远,则更新canReachMost;
- 判断退出循环的条件是否为因为已经可以到达终点。
代码详情
bool canJump(vector<int>& nums) {
int n = nums.size(), canReachMost = 0;
for (int i = 0; i < n; ++i) {
if (i > canReachMost || canReachMost >= n - 1) break;
canReachMost = (canReachMost > i + nums[i]) ? canReachMost : i + nums[i];
}
return reach >= n - 1;
}
嗯,代码是十分简单的,它的复杂度为O(n)。
在搜索Greedy算法题目时,同时也搜索到了这个题目的第二版——Jump Game II,所以就一并做了。
题目是类似的,但是这时给出的数组是保证了能够到达终点的,但是这题的要求也变了——求出能够到达终点所需的最小的跳跃步数。很显然,这也是一个贪心的情景,因为我们想着每次跳跃到达的位置刚好可以满足能够跳得同时又跳跃尽可能少的次数。这时,我们不能一味地跳最远,而是查看当前能够跳到的最远的位置是否存在更好的跳跃方案再进行跳跃,此时,我们维护两个变量来模拟跳跃过程,lastToFurthest和currToFurthest,分别表示上一次能够跳跃到的最远位置和从当前位置能够跳跃到的最远位置,使用count来记录步数,遍历数组,如果当前位置和使用当前位置的能量能够到达的位置比currToFurthest能到达的最远位置更远,则更新currToFurthest,如果当前位置到达了上一次的能够到达的最远位置,那么,我们需要再次跳跃了,count自增,同时lastToFurthest置为currToFurthest,因为已知肯定能够到达终点的,所以当当前能够跳跃的最远位置大于或等于终点时,即可退出循环,得到最小跳跃次数。
代码详情如下:
int jump(vector<int>& nums) {
int goal = nums.size() - 1;
int currToFurthest = 0, lastToFurthest = 0;
int count = 0;
for (int i = 0; i < goal; i ++) {
currToFurthest = (currToFurthest > i + nums[i]) ? currToFurthest : i + nums[i];
if (i == lastToFurthest) {
count ++;
lastToFurthest = currToFurthest;
if (currToFurthest >= goal) break;
}
}
return count;
}
谢谢阅读