跳跃游戏Ⅰ
这一题比较简单,首先需要明确的是只有当走到了元素为0的下标时才不能达到最后一个位置。所以在循环中直接考虑nums[i]==0的条件i之前的数能不能跳过当前位置。
public boolean canJump(int[] nums) {
if(nums.length<=1) return true;
for(int i=0;i<nums.length-1;i++){
flag://在这里设置一个flag标志是当下面语句成立时即表示可以跳过这个0的位置,需要跳出整个if(语句);
if(nums[i]==0){//
for(int j=0;j<i;j++){
if(nums[j]+j>i) break flag;
}
return false;
//if(j==i) return false;
}
}
return true;
}
跳跃游戏Ⅱ
首先该题默认是可以到达最后一个元素的,因为只要能跳到或跳过最后一个数就完成了,reach表示当前能跳到的位置范围,比如第一跳只能从0开始,第二跳就可以从1到它能跳的最远距离范围内开始跳,每次循环更新nextreach的值,那么以i<=reach作为循环条件,每一个位置跳跃的最远距离为i+nums[i],如果可以跳过,就可以返回跳数,否则这一次跳不完,那么就先跳到最远位置,并更新reach。
public int jump(int[] nums) {
if(nums.length<=1) return 0;
int reach=0,jumpNums=0;
int nextreach=nums[0];
for(int i=0;i<=reach;i++){
nextreach=Math.max(i+nums[i],nextreach);//统计能到达下一位置的最远距离
if(nextreach>=nums.length-1) return (jumpNums+1);
if(i==reach){//这里表示当前位置能跳到的最远距离。
jumpNums++;
reach=nextreach;
}
}
return jumpNums;
}
跳跃游戏Ⅲ
前面三题还是挺简单的,第三题这一看刚开始想用迭代去实现,但是试试递归设定好条件就非常简洁明了。
public boolean canReach(int[] arr, int start) {
if(start<0||start>=arr.length) return false;
if(arr[start]<0) return false;
if(arr[start]==0) return true;
int right=arr[start]+start;
int left=start-arr[start];
arr[start] *=-1;//将当前值设置为负数,如果跳回当前位置表示循环无法跳到指定0所在下标位置;
return canReach(arr,left)||canReach(arr,right);
}
跳跃游戏Ⅳ
这题广度优先搜索的思路,可以直接从最后一个点倒着去遍历每一步可达的点,如果可以到达下标为0的点,可直接返回当前的步数。
设置队列来存储下一步可到达的所有点;
设置Set用来存储所有已经遍历过的点,保证每个点只入队出队一次;
然后设置Map<,Set>用来存储每个值对应的下标(可能不只一个),并不断更新,到达的点会删除。
public int minJumps(int[] arr) {
if (arr.length == 1)
return 0;
Map<Integer, Set<Integer>> value2Index = new HashMap<>();
for (int i = 0; i < arr.length; i++) {
value2Index.computeIfAbsent(arr[i], k -> new HashSet<>()).add(i);
}
int minStep = 0;
Set<Integer> set = new HashSet<>();
Queue<Integer> nextStep = new LinkedList<>();
nextStep.add(arr.length-1);
set.add(arr.length-1);
while (!nextStep.isEmpty()) {
int count = nextStep.size();
minStep++;
for (int i = 0; i < count; i++) {
int index = nextStep.poll();
value2Index.get(arr[index]).remove(index);
Set<Integer> temp = new HashSet<>(value2Index.get(arr[index]));
if (index - 1 >= 0) {
temp.add(index-1);
}
if (index + 1 < arr.length) {
temp.add(index + 1);
}
for (int a : temp) {
if (a == 0) {
return minStep;
}
if (!set.contains(a)) {
set.add(a);
nextStep.add(a);
}
}
}
}
return minStep;
}
跳跃游戏Ⅴ
思路
首先根据题目描述可知,只能从高台阶往低台阶跳且不能越过比当前台阶高的台阶跳向其他台阶,那么要跳最多次,一定是在能跳跃的范围内按高低顺序(已跳跃所在位置为准)依次跳跃才能达到最多次数。
站在一根柱子上,既可以往左跳也可以往右跳,只要在范围内。对于任意的柱子,能访问到的柱子数等于往左或往右跳到的那一个柱子所能访问的数目再加它本身。所以可以用一个一维数组 dp[] 来存放每个柱子最多能访问的数量。
使用递归可以利用 dp[] 的值来免去排序之类的处理,使得解法更加简洁快速。
这里使用 dp 数组来记忆的一个技巧就是避免重复计算。对于每一根柱子,他最小的访问数目是1,就是他自己(比如左右紧挨着的柱子都比它高),数组初始化的时候每一个元素都是0,后面的遍历中,如果取到的柱子i有 dp[i]!=0,说明这个柱子已经计算过了,直接取这个值就可以用,而无需重新计算;如果dp[i]是0,就要获取它左边或右边数目最大的柱子,再+1。注意一下限制条件。
动态规划啊,递归还是得努力掌握,可以解决很多复杂点,清楚思路或者它的示例步骤却没法代码实现,是大家的痛…没有捷径,只能不断地去摸索练习。
class Solution {
private int[] arr;
private int n; //数组长度
private int d;
private int[] dp; //用来存储每个柱子的最大结果
public int maxJumps(int[] arr, int d) {
this.arr = arr;
this.n = arr.length;
this.d = d;
dp = new int[n];
int ans = 0;
for(int i=0; i<n; i++){
ans = Math.max(ans, getMaxFromOnePoint(i));
}
return ans;
}
private int getMaxFromOnePoint(int p){
if(dp[p] != 0) return dp[p]; //当前柱子已经计算过,直接返回它的值
// 如果没有,分别计算它往左和往右跳一次可以得到的最大值
int leftMax = 0;
int left = 1; // 往左跳的距离
while(p-left>=0 && left<=d){
if(arr[p-left]>=arr[p]){ //遇到了高柱子挡路,只能结束
break;
} else{
if(dp[p-left]==0) dp[p-left] = getMaxFromOnePoint(p-left);
leftMax = Math.max(leftMax, dp[p-left]);
left++;
}
}
// 同理右边
int rightMax = 0;
int right = 1;
while(p+right<n && right<=d){
if(arr[p+right]>=arr[p]){
break;
} else{
if(dp[p+right]==0) dp[p+right] = getMaxFromOnePoint(p+right);
rightMax = Math.max(rightMax, dp[p+right]);
right++;
}
}
return Math.max(leftMax, rightMax)+1;
}
}
参考资料:跳跃游戏Ⅴ题解