力扣打卡:55. 跳跃游戏
解题思路
分析
状态分析
题目给定的要求 0
号位置是否可以到达最后一个节点,那么需要求在跳跃范围内的每一个节点是否可以到达最后一个节点
对于每一个元素的跳跃范围内的各个元素,只需要一个可以到达最后一个节点,那么当前的这个节点就可以到达最后一个节点
暴力递归
- 对于当前的元素,只要跳跃范围内的元素有一个可以到达最后一个节点,那么当前节点就可以到达最后一个节点
- 对于当前的元素的跳跃范围内的元素,只要其自身的跳跃范围内内的元素有可以到达最后一个节点的,那么可以到达最后一个节点
- 依次类推
自顶向下分析
- 相比起暴力递归,只是多了判断和记录的两个流程,其他一致
自底向上
自底向上的分析方法,一般都是和自顶向下的分析对象、分析流程是相反的
- 对于自顶向下分析:从0号位置开始,判断末尾节点,中间记录每个节点的跳跃范围内是否可以达到最后一个节点
- 对于自底向上分析:从末尾开始,因为末尾就是最后一个节点,肯定能够到达
- 从 末尾 到 开头 顺序检查(和自顶向下的检查流程刚好相反)
- 首先定义一个外层的循环,从
末尾
到开头
的每一个元素的检查 - 其次定义一个内部循环,表示检查当前元素的跳跃范围
- 最后返回节点即可
代码
class Solution {
public boolean canJump(int[] nums) {
// 定义一个函数,从c开始检查,检查当前的位置 p + nums[p] 的范围内是否有元素可以到达
// return planA(0, nums);
// boolean[] memo = new boolean[nums.length]; // 如果是0,那么表示未初始化,如果为1,表示能到达,如果为-1,表示无法到达
// return planB(0, nums, memo);
boolean[] memo = new boolean[nums.length];
return planC(nums, memo);
}
// 暴力递归的写法
// 分析:
// 每一个数组元素代表的都是跳跃的最长长度,那么分析每一个元素即可
// 对于第一个元素,分析 0-最长长度的每个元素是否可以到达末尾
// 末尾的终止条件应该是 当前的坐标大于等于nums.length-1,表示最长的长度超过了最后一个
// 返回false的只有 在当前的最长长度内的所有元素都不能到达末尾的时候,就返回false
public boolean planA(int p, int[] nums){
if(p >= nums.length-1) return true;
boolean possibility = false;
for(int i=p+1; i<=p+nums[p]; i++){
possibility = planA(i, nums);
if(possibility) return true; // 如果有可能,直接return即可
}
return false;
}
// 写好了暴力递归,那么自顶向下的分析就是多了判断和记录的过程
public int planB(int p, int[] nums, int[] memo){
if(p >= nums.length-1) return 1;
if(memo[p] != 0) return memo[p]; // 如果检查是否已经计算过,如果计算过,检查是否为true,如果true,那么返回true
int possibility = -1;
for(int i=p+1; i<=p+nums[p]; i++){
possibility = planB(i, nums, memo);
if(possibility == 1) break;
}
memo[p] = possibility;
return memo[p];
}
// 此时用递推在数组长度大时,会出现栈溢出的现象,那么此时改为递推
// 改为递推的过程,一般要与递归的递归顺序相反,此时从0开始,那么相反的顺序应该从最后开始
// 从最后开始,那么最后一个元素肯定是可以到达最后一个元素的
// 那么递推的话:从最后一个开始检查,检查在自己的范围内是否可以到达最后一个节点
// memo的定义就为 定义为 当前的节点是否可以到达最后一个节点
public boolean planC(int[] nums, boolean[] memo){
for(int i=nums.length-1; i>=0; i--){ // 从最后开始检查每一个元素
if(i==nums.length-1) {memo[i]=true; continue;} // 首先将最后一个元素置为true,表示初始化basecase
int len = Math.min(i+nums[i], nums.length-1); // j<= p+nums[p],nums.length-1,索引范围取小值即可
for(int j=i+1; j<=len; j++){ // 遍历跳跃范围内是否有为true的元素
if(memo[j]==true) { // 判断在跳跃范围内是否有true
memo[i] = true; // 记录状态
break;
}
}
}
return memo[0];
}
}