给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
示例 2:
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
提示:
1 <= nums.length <= 3 * 104
0 <= nums[i] <= 105
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/jump-game
方法一:利用队列,道理和层序遍历时一样的
队列存放是从从队列中跳出的节点 index 可以到达的位置。所以每次跳出一个节点,只要判断index是否是数组最后一个下标即 index 是否等于 nums.length - 1即可,如果相等,说明可以到达,否则不可以到达。如果将整个队列遍历完之后,直接返回false,说明没有办法到达.
对应的代码:
class Solution {
/*
利用的是层序遍历的思想
1、将当前位置可以到达的位置压入到队列中
2、从队列中跳出一个节点,然后判断这个位置能不能到达终点,如果不能,那么判断它的值是否为0,如果为0,不做操作,
否则就将它可以到达的位置再次压入到队列中
3、当队列为空的时候,直接返回false
*/
public boolean canJump(int[] nums) {
int i,index,j,size;
Queue<Integer> queue = new LinkedList<Integer>();
boolean[] visited = new boolean[nums.length];
queue.offer(0);
while(!queue.isEmpty()){
size = queue.size();
for(i = 0; i < size; i++){
index = queue.poll();
if(index == nums.length - 1) //从队列中跳出一个元素,如果这个元素刚好是数组最后一个下标,直接返回true
return true;
/*
这一步可以不写,因为for循环中已经进行了判断,如果当前index对应的值是0,for循
环中j初始值是j = index + 1,那么会因为j > index + nums[index]而不会执行for循
环中的内容
if(nums[index] == 0)
continue;
*/
for(j = index + 1; j < nums.length && j <= index + nums[index]; j++){
/*
注意为了避免超时,需要定义一个数组,标记j这个位置是否已经压入到队列了,如果是,那么
就不要再次压入到队列中了,从而使得队列的长度减少,进而降低时间复杂度。
例如[3,2,1,1],当index = 0的时候,就已经将2,1,1对应的下标1,2,3压入到队列中了
如果没有这个if判断,直接将j压入,那么导致下一次while循环中,当跳出2的时候,还需将
1,1对应的2,3下标压入到队列中,从而增加了时间复杂度。
同样的,为了标记j这个位置是否已经被访问了,不可以再将j位置压入到队列之后,就将它的
值更新成为一个负数,尽管这个题目中的所有元素都是非负数,因为再这个for循环中需要用
到j对应的值,一旦在将j位置压入到队列的时候,那么当从队列中跳出的下标index为j的时候
那么index对应的值就变成了一个负数,从而就没有办法获取index可以到达的位置了,所以
这里是重新定义一个数组来标记是否已经被访问过了
*/
if(!visited[j]){
queue.offer(j);
visited[j] = true;
}
}
}
}
return false;
}
}
运行结果:
方法二:贪心算法
class Solution {
public boolean canJump(int[] nums) {
int i,j,rightMost;
rightMost = 0;//表示i下标能够到达的最远的位置下标
for(i = 0; i <= rightMost && i < nums.length; i++){
/*
再当前的元素没有超过能到到的最右边的位置的时候,那么就更新rightMost,如果更新之后
的rightMost大于等于nums.length - 1,直接返回true,否则继续更新,一旦超过了
rightMost,直接返回false
*/
rightMost = Math.max(rightMost,i + nums[i]);
if(rightMost >= nums.length - 1)
return true;
}
return false;
}
}
运行结果:
LeetCode 跳跃游戏II
给你一个非负整数数组 nums ,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。假设你总是可以到达数组的最后一个位置。
示例 1:
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
示例 2:
输入: nums = [2,3,0,1,4]
输出: 2
提示:
1 <= nums.length <= 104
0 <= nums[i] <= 1000
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/jump-game-ii
方法一:利用队列
对应的代码:
class Solution {
public int jump(int[] nums) {
Queue<Integer> queue = new LinkedList<Integer>();
boolean[] visited = new boolean[nums.length];
queue.offer(0);
visited[0] = true;
int i,index,j,size;
int count = 0;
while(!queue.isEmpty()){
/*
队列存放的是跳跃第count次的时候到达的所有可能位置,所以一旦从队列中跳出的值刚好是最后一个
元素的下标,就可以直接返回count,否则,就将当前跳出的值可以到达的位置压入到队列中。当for循环
结束之后才执行count + 1,这样才可以保证队列中的元素才是跳跃第count + 1次的所有可能位置
*/
size = queue.size();
for(i = 0; i < size; i++){
index = queue.poll();
if(index == nums.length - 1)
return count;
/*
可以不写
if(nums[index] == 0)//如果当前下标对应的值是0,那么直接进行下一次循环
continue;
*/
for(j = index + 1; j < nums.length && j <= index + nums[index]; j++){
if(!visited[j]){
/*
注意不可以将这个判断改成!visited[j] && nums[j] != 0
因为有可能最后一个元素是0,那么这时候如果if判断是加上了nums[j] != 0,那么就
会导致最后一个元素没有添加到队列中,从而导致错误
*/
queue.offer(j);
visited[j] = true;
}
}
}
count++;//在for循环结束之后才执行,从而将跳跃第count + 1的所有可能压入到队列中
}
return -1;
}
}
运行结果:
方法二:贪心
在理解上面的方法之后,在看这个代码会更加容易理解。
其中if(i == end)这个判断相当于上面的代码中for循环结束之后进行count++.
class Solution {
public int jump(int[] nums){
int count = 0,i,farthest = 0,end = 0;
for(i = 0; i < nums.length - 1;i++){
farthest = Math.max(farthest,nums[i] + i);//获取当前下标能够到达的最远位置
if(i == end){
//更新最远farthest的时候,count++
end = farthest;
count++;
}
}
return count;
}
}
运行结果: