题目描述:
每个点的数字代表可以从当前节点跳跃最远的节点数,比如 [ 2, 3, 1, 1, 4 ] 第一个节点跳跃最远位置就是下标为2的第三个元素。
求跳到最后一个节点最少需要跳跃多少次?
解析:
说实话,这题长得就像一个典型的动归,甚至忍不住往动归想。但是俗话说嘛,能用简单的方法解决就用简单的方法解决,所以尝试一下贪心,不过试一下确实是有戏。
对吧,首先要用贪心算法先要把定义搬上来:在对问题求解时,总是做出在当前看来是最好的选择。
那么我们先对当前状况选择,就拿题目的示例: [ 2, 3, 1, 1, 4 ]
最开始的子任务,第一个元素肯定不用考虑了,直接为0,[2],一开始就在第一个位置上不用跳。
第二个位置,[ 2, 3 ],那么跳法肯定是 index:0 -> index:1
第三个位置,[ 2, 3, 1 ],对于第三个元素,上一跳可以是第一个元素,可以是第二个元素,那么就是一个选择的问题了,贪心算法告诉我们要选现在看起来最好的,原本覆盖范围内越远越好:
那其实很好选,第三个元素在第一个元素的覆盖范围内,所以选上面那种是最好的。
所以得出第一个思路:在上次选择节点的覆盖范围内,我们不需要进行多余的跳跃(如上面例子,第一个元素2是上次选择节点,对于第三个元素不需要多余的跳跃从2 -> 3 -> 1)
第一个思路提供了覆盖得到的情况下的最好选择,那么问题又来了,如果覆盖不到了咋整,那就必须进行跳跃,那么问题又来了,跳跃从上个节点跳到哪个节点上?当前节点?还是上次节点到当前节点的某个节点上?
解决这几个问题基本就可以收工了,当我们必须要跳跃的情况下,贪心算法说:我们要选现在看起来最好的,很明显覆盖范围越远越好,那么就很好选了,选上次节点到当前节点之间覆盖最远那个节点,说的有些抽象,可以看图:
那么两个思路就出来了:
1. 在上次选择节点的覆盖范围内,我们不需要进行多余的跳跃
2. 在上次选择节点覆盖范围最远的那个点,必须进行跳跃,选择 [上次选择节点, 上次选择节点覆盖范围最远的那个点] 之间覆盖最远的节点,理解看上图
根据两个思路实现
代码:
class Solution {
public:
int jump(vector<int>& nums) {
//总长度
int len = nums.size();
//上一个被选中结点最远的覆盖范围
int lastJumpCoverage = 0;
//最终跳跃的次数
int resultMin = 0;
//记录走过节点能覆盖的最远距离的状态
int theFarthestDistance = 0;
for(int i = 0; i < len; i++){
//如果已经到最后一个了就直接break掉(有[0]情况)
if(i == (len - 1)) break;
if(lastJumpCoverage == i){
theFarthestDistance = max((i + nums[i]), theFarthestDistance);
//上次选的结点覆盖不到了,重新更新结点,当前节点最远距离和之前节点能到达的最远距离取最大
lastJumpCoverage = theFarthestDistance;
resultMin += 1;
}else{
//覆盖得到
theFarthestDistance = max((i + nums[i]), theFarthestDistance);
}
}
return resultMin;
}
};
自己举了几个例子,算法都能满足,唯一不满足的可能就是类似 [ 0, 1 ], 第一个元素位置永远跳不到第二个元素的位置,这种情况不知道输出啥,但是看题目没说明就不管他了。
给 [0] 卡了一下