#贪心算法
跳跃游戏1——能否到达终点:
LeetCode55:
给你一个非负整数数组 nums
,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标,如果可以,返回 true
;否则,返回 false
。
示例 1:
输入:nums = [2,3,1,1,4] 输出:true 解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
示例 2:
输入:nums = [3,2,1,0,4] 输出:false 解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
思路分析:
该问题中,我们只需要判断最终能否到达终点,不用思考每一步要具体跳跃到哪个位置,而是尽可能跳到最远的位置,看最远能够覆盖到哪里,只需要定义一个cover变量,来存放当前能覆盖到的最远位置,只要cover能够覆盖到末尾即可:
例如,在上图1中,3能够覆盖的范围是{2,1,0},2接下来能覆盖的是{1,0},1只能覆盖到{0},所以无法到达最终的4.
而在第二个数组中,2能覆盖{3,1},3可以覆盖{1,1,4},已经找到一条能够到达末尾的通路。1只能到下一个1,下一个1能够到达4,所以有{2,1,1,4},{2,3,1,1,4}两种走法,加起来有三种走法。
我们可以让变量cover表示能够到达的最远方位,将每次遍历的范围限制在i<=cover内,这样就能从前向后不断地扩大cover的范围,直到cover覆盖到终点,或i == cover时仍未到达终点。
cover可以这样进行更新:
cover = max(遍历的当前元素所能到达的最远位置,cover自身)
在上述例子中可以表示为:
- 在第二个图中,nums[0] = 2,此时cover = 2,能覆盖到{3,1};
- 继续,遍历到第二个元素(i=1),此时能覆盖的范围就是1+3,能覆盖{1,1,4}三个节点,此时的cover,对cover进行更新,cover=max(1+3,2),更新之后cover满足cover>=nums.length-1,遍历结束。
代码如下:
class Solution {
public boolean canJump(int[] nums) {
//如果长度为1,直接到达终点;
if(nums.length == 1) return true;
//定义cover来存储能到达的最远位置;
int cover = 0;
//数组的遍历范围需要限制在cover内,这样才能正确判断能否到达终点;
for ( int i = 0 ;i<=cover; i++){
//i+nums[i]表示当前节点能到达的最远位置,用它来和cover对比和更新;
cover = Math.max(cover , i+ nums[i]);
if(cover >= nums.length-1)
return true;
}
return false;
}
}
跳跃游戏2——最短跳跃游戏:
LeetCode45:
给定一个长度为 n
的 0 索引整数数组 nums
。初始位置为 nums[0]
。
每个元素 nums[i]
表示从索引 i
向前跳转的最大长度。换句话说,如果你在 nums[i]
处,你可以跳转到任意 nums[i + j]
处:
0 <= j <= nums[i]
i + j < n
返回到达 nums[n - 1]
的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]
。
示例 1:
输入: nums = [2,3,1,1,4] 输出: 2 解释: 跳到最后一个位置的最小跳跃数是2。从下标为0跳到下标为1的位置,跳1步,然后跳3步到达数组的最后一个位置。
示例 2:
输入: nums = [2,3,0,1,4] 输出: 2
解题思路:
此问题在上一题基础上要求最少跳跃次数,可以借助双指针和贪心算法来解决:
我们用left表示当前遍历的节点,在遍历时,首先记录该节点能到达的最远位置right,然后用一个变量steps来记录到达当前位置的最小步数,还需要一个临时变量cover,用来记录当前节点left能到达的最远位置,只有当left能够到达right的位置时,才能更新right和steps。
在图中,开始元素是2,首先让right到达从该点一步所能到达的最远位置nums[2],此时steps=1,也就是说只走一步最远只能到达nums[1],此时cover=nums[0]+0=2;
接下来需要开始讨论再走一步的情况,因为此时left和right中间的点都可以作为第二步的起点,所以我们继续遍历即可,当step=2时,此时可以作为起点的元素是{3,1},3能让我们到达left+nums[left] = 1 + 3 = 4,而1能让我们到达的最远位置是left+nums[left] = 2 + 1 = 3,所以我们将cover更新为4.
然后用left和right重新标记step=2能到达的区间:
此时还没有到达终点,需要继续走,现在left和right之间能作为起点的分别为{2,4},如果选择2可以到达left+nums[left] = 3 + 2 = 5,如果选择4则可以到达left+nums[left] = 4 + 4 =8,已经超越末尾了,此时step = 3,所以可以知道最少需要走三次才能到达终点。
Java代码实现如下:
class Solution {
public int jump(int[] nums) {
int right = 0;
int steps = 0;
int cover = 0;
//left从头开始遍历;
for(int left = 0; left < nums.length-1 ; left++){
//cover记录当前steps下,能到达的最远位置;
cover= Math.max(nums[left] + left ,cover);
//当left == right时,表示已经计算完了该steps下,能走到的最远位置;
//将right更新为cover,将最短步数steps++;
if(left == right){
right = cover;
steps ++ ;
}
}
if(right >= nums.length-1)
return steps;
return steps;
}
}