Leetcode——跳跃游戏1,2,3

这篇博客详细介绍了跳跃游戏的几种解决方案,包括贪心、模拟、深度优先搜索(DFS)和广度优先搜索(BFS)。对于跳跃游戏1和2,提供了不同策略的Java代码实现,强调了在贪心策略中避免多余的跳跃。跳跃游戏3则通过BFS和DFS寻找能到达数字0的路径。所有解决方案都考虑了如何优化搜索过程和减少步数。
摘要由CSDN通过智能技术生成

1. 跳跃游戏

在这里插入图片描述

(1)贪心

不用拘泥于每次究竟跳跳几步,而是看覆盖范围,覆盖范围内一定是可以跳过来的,不用管是怎么跳的。

class Solution {
    public boolean canJump(int[] nums) {
        if (nums.length == 1) {
            return true;
        }
        //覆盖范围
        int coverRange = nums[0];
        //在覆盖范围内更新最大的覆盖范围
        for (int i = 0; i <= coverRange; i++) {
            coverRange = Math.max(coverRange, i + nums[i]);
            if (coverRange >= nums.length - 1) {
                return true;
            }
        }
        return false;
    }
}

(2)模拟

class Solution {
    public boolean canJump(int[] nums) {
        int max = 0;
        int len = nums.length;

        //max相当于现在可以到达的最远位置
        //遍历数组 从索引为0的地方开始  
        for (int i = 0; i < len; i++) {
            //最远都不能到达索引为i处(i是 ++的)无法继续前进  直接返回false
            if(i > max) 
                return false;           
            //实时更新可以到达的最远位置
            max = Math.max(max,i+nums[i]);        
        }
        return true;
    }
}


2. 跳跃游戏2

在这里插入图片描述

(1)DFS(不推荐)

DFS比较符合正常人的解题思路,以cur为起点,进行递归。
nums[cur]获取最大跳数
遍历1~nums[cur]次,计算next的位置
剪枝条件:如果minSteps[next] <= minSteps[cur] + 1; 已有方案更好,不需要递归了
终止条件:可选

if (minSteps[cur] > minSteps[nums.length - 1]) {
	return;
}

代码:

public class Solution {
    int[] minSteps;

    public int jump(int[] nums) {
        //排除特例
        if (nums == null || nums.length <= 1) 
            return 0;
        
        //存储到当前位置,所需要的最小步数
        minSteps = new int[nums.length];    
        Arrays.fill(minSteps, Integer.MAX_VALUE);

        minSteps[0] = 0;
        dfs(nums, 0);
        return minSteps[nums.length - 1];
    }

    private void dfs(int[] nums, int cur) {
        for (int next = cur + 1; next < nums.length && next <= cur + nums[cur]; next++) {
            // 剪枝。当前的方案比已有的方案还差,就不要递归了
            if (minSteps[next] <= minSteps[cur] + 1) 
                continue; 

            minSteps[next] = minSteps[cur] + 1;
            dfs(nums, next); // 递归尝试所有跳数
        }
    }
}

(2)BFS(不推荐)

优化办法自然是避免每个元素都遍历,所以会想到BFS:
只统计每一层最远距离,如果某一层最大index覆盖了最后一个元素,则当前层就是最小跳数。
只需O(N)复杂度就能算出每一层的最大index

  • 其实每到达一个点i,都会有nums[i]种跳跃选择,可以把每一轮的跳跃都加入队列中,然后再依次出队列,是可以得到最快到达终点的解的
  • 其实是可以用优先队列的,不过我们自己保障了普通队列的顺序也行,按照跳跃距离从大到小入队列,对最终到达target是有帮助的
class Solution {
    public int jump(int[] nums) {
        int n = nums.length;
        int ans = 0;
        boolean[] st = new boolean[n];
        Deque<Integer> d = new ArrayDeque<>();
        st[0] = true;
        d.addLast(0);

        while (!d.isEmpty()) {
            int size = d.size();
            while (size-- > 0) {
                int idx = d.pollFirst();
                //如果走到了最后一个位置
                if (idx == n - 1) 
                    return ans;
                
                for (int i = idx + 1; i <= idx + nums[idx] && i < n; i++) {
                    if (!st[i]) {
                        st[i] = true;
                        d.addLast(i);
                    }
                }
            }
            ans++;
        }
        return ans;
    }
}

(3)贪心(注意一个细节)

在这里插入图片描述

  • 要注意一个细节,就是 for 循环中,i < nums.length - 1,少了末尾。
  • 因为开始的时候边界是第 0 个位置,steps 已经加 1 了。如下图,如果最后一步刚好跳到了末尾,此时 steps 其实不用加 1 了。如果是 i < nums.length,i 遍历到最后的时候,会进入 if 语句中,steps 会多加 1。

思路过程:

最开始遍历 i = 0, end = 0,因此 step 会进行 step ++,我们可以认为,这是开始起跳,因为必定会落下,因此跳跃次数 + 1
而 nums[0] 这个数限制了你只能在落脚在某个范围内,假如 nums[0] = 4,那么你只能选择落脚在 [1, 4] 位置,而如果到了边界,那么肯定是一次新的起跳,因此次数需要再 + 10 位置开始起跳,你落脚的必定是 [1, 4] 位置中能够跳得更远的,因为根据贪心思想,这样做能够尽可能的减少跳跃次数,因为更加接近最后一个位置
而在这个过程遍历 [1, 4] 过程中一直记录着最远位置 k,而你落地在 [1, 4] 之间,落地的那个点也就是 [1, 4] 之间最能够跳得远的那个位置,因此当到达边界的时候,将 end 更新为 k

注意:[1, 4] 跳得最远的位置必定不会在 [1, 4] ,因为如果在 [1, 4] ,那么表示根本就出不去 [1, 4] 这个圈
当然不会是 [4,1,1,1,0,1,2] 这种的,因为如果是这种的,压根过不去这个 0,因此必定第一次起跳必定能够跳出 [1, 4] 这个范围,比如 [4,1,1,1,1,1,0]
        
class Solution {
    public int jump(int[] nums) {
    	//排除特例
        if (nums == null || nums.length <= 1) 
            return 0;
        
        int end = 0;
        int maxPosition = 0; 
        int steps = 0;
        //注意: i < nums.length - 1
        //这里有个小细节,因为是起跳的时候就 + 1 了,如果最后一次跳跃刚好到达了最后一个位置
        //那么遍历到最后一个位置的时候就会再次起跳,这是不允许的,因此不能遍历最后一个位置
        for (int i = 0; i < nums.length - 1; i++) {
            //找能跳的最远的
            maxPosition = Math.max(maxPosition, nums[i] + i); 

            //遇到边界,就更新边界,并且步数加一
            if (i == end) { 
                end = maxPosition;
                steps++;
            }
        }
        return steps;
    }

}

避免最后一次跳跃刚好到达了最后一个位置,然后多余起跳,其实这样最好理解:

class Solution {
    public int jump(int[] nums) {
        //排除特例
        if (nums == null || nums.length <= 1) 
            return 0;
        
        int end = 0;
        int maxPosition = 0; 
        int steps = 0;

        for (int i = 0; i < nums.length; i++) {
            //找能跳的最远的
            maxPosition = Math.max(maxPosition, nums[i] + i); 

            //说明当前一步,再跳一步就到达了末尾
            if (maxPosition >= nums.length - 1) {
                steps++;
                break;
            }

            //遇到边界,就更新边界,并且步数加一
            if (i == end) { 
                end = maxPosition;
                steps++;
            }
        }
        return steps;
    }

}

(4)贪心(最好理解)

以最小的步数增加最大的覆盖范围,直到覆盖范围覆盖了终点,这个范围内最小步数一定可以跳到,不用管具体是怎么跳的,不纠结于一步究竟跳一个单位还是两个单位。

class Solution {
    public int jump(int[] nums) {
        //排除特例
        if (nums == null || nums.length <= 1) 
            return 0;
        
        //记录跳跃的次数
        int count=0;
        //当前的覆盖最大区域
        int end= 0;
        //最大的覆盖区域
        int max = 0;
        for (int i = 0; i < nums.length; i++) {
            //在可覆盖区域内更新最大的覆盖区域
            max = Math.max(max, i + nums[i]);
            
            //说明当前一步,再跳一步就到达了末尾
            if (max >= nums.length-1){
                count++;
                break;
            }
            
            //走到当前覆盖的最大区域时,更新下一步可达的最大区域
            if (i == end){
                end = max;
                count++;
            }
        }
        return count;
    }
}

3. 跳跃游戏 3

在这里插入图片描述

(1)BFS

  • 用一个队列装入将要到达的下一步的位置下标,每个已经到达的地方就不需要再去继续遍历了
  • 所以这里使用visited数组标记已经到达的地方,一定要清楚如果一个地方能到达数字0那么在第一次遍历也就是visited中位置还是false的时候就能找到0
  • 就好比一条路要能去曹县那么踏上这条路必定会到,而第一次都没到后面再踏上这条路肯定也不能到。之后的工作就是能到的下标放进队列,直到找到数字0为止,没找到就是返回false;
class Solution {
    public boolean canReach(int[] arr, int start) {
        int len = arr.length;
        boolean[] visited = new boolean[len];
        Queue<Integer> queue = new LinkedList<>();
        queue.add(start);

        //遍历x + arr[x] 与 x - arr[x] 两个方向
        while (!queue.isEmpty()) {
            int x = queue.poll();
            if (x + arr[x] < len && !visited[x + arr[x]]) {
                visited[x + arr[x]] = true;
                queue.add(x + arr[x]);
                if (arr[arr[x] + x] == 0)
                    return true;
            }
            if (x - arr[x] >= 0 && !visited[x - arr[x]]) {
                visited[x - arr[x]] = true;
                queue.add(x - arr[x]);
                if (arr[x - arr[x]] == 0)
                    return true;
            }
        }
        return false;
    }
}

(2)DFS

  • 其实和广度优先搜索基本一样,只不过深度搜索是往下递归下一步去哪,而广度搜索采取队列存放下一步要到哪。核心的visited标记数组不能少,依然是走过的死胡同不能再走。

跳出递归的条件:

  • index < 0 || index >=arr.length return false
  • index 已经是访问过的下标 return false
  • arr[index] == 0 return true

借助visited布尔数组来存储已经访问过的下标

class Solution {
    public boolean canReach(int[] arr, int start) {
        boolean[] visited = new boolean[arr.length];
        return dfs(arr, visited, start);
    }

    public boolean dfs(int[] arr,boolean[] visited,int start){
        if (start < 0 || start >= arr.length)
        	return false;

        if(arr[start] == 0)
        	return true;

        if (visited[start]) {
            return false;
        } else {
            visited[start]=true;
        }

        return dfs(arr, visited, start + arr[start]) || dfs(arr, visited, start - arr[start]);
    }
}

也可以这样写:

class Solution {
    int[] arr;
    boolean[] visited;


    public boolean canReach(int[] arr, int start) {
        this.arr = arr;
        this.visited = new boolean[arr.length];
        return dfs(start);
    }

    public boolean dfs(int start) {
        if (start < 0 || start >= arr.length)
            return false;
        
        if (arr[start] == 0)
            return true;
        
        if (visited[start])
            return false;
        else
            visited[start] = true;

        return dfs(start + arr[start]) || dfs(start - arr[start]);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yawn__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值