跳跃游戏 (DFS->记忆化搜索->动态规划/贪心证明)

news/2025/1/8 20:12:21/

一.跳跃游戏简单介绍

1. 跳跃游戏简单介绍

        跳跃游戏是一种典型的算法题目,经常是给定一数组arr,从数组的某一位置i出发,根据一定的跳跃规则,比如从i位置能跳arr[i]步,或者小于arr[i]步,或者固定步数,直到到达某一位置,可能是数组的最后一个位置,也有可能是某一特别的数值处,也有可能在这个过程中,可能需要求解可能存在的最大值或者最小值。

        对于跳跃游戏类的题目,经常使用贪心、动态规划、dfs、bfs等方法解决,对于可以使用dfs解决的题目,经常也可以使用动态规划,但一般贪心可以有更好的时间复杂度和空间复杂度。还有经常使用的动态规划剪枝、前缀和、滑动窗口和BFS,由于在大部分情况下,能用DFS解决的题目都可以用BFS解决,且两种方法有基本相同的复杂度,尤其是在跳跃游戏这类题目中,可以视为一种方法。

        本文借青蛙酱主要复习动态规划三部曲。

2.跳跃游戏典型题目

(1)leetcode70 爬楼梯

(2)leetcode剑指 Offer 10- II. 青蛙跳台阶问题

(3)剑指 Offer II 088. 爬楼梯的最少成本

(4) leetcode55 跳跃游戏

(5)leetcode45 跳跃游戏 II

(6) leetcode1306 跳跃游戏 III

(7)leetcode1696 跳跃游戏 VI

(8)leetcode1871 跳跃游戏 VII

(9)leetcode1413 逐步求和得到正数的最小值

以上部分,请见:

跳跃游戏 (贪心/动态规划/dfs)

跳跃游戏 (动态规划剪枝/前缀和/滑动窗口/BFS剪枝)

3.动态规划三步曲复习

Java-算法-动态规划

二.跳跃游戏相关专题-青蛙酱🐸的奇妙冒险

1. 剑指 Offer 10- II 青蛙跳台阶问题

见 一.2.(2)

2. leetcode 2498 青蛙过河 II

给你一个下标从 0 开始的整数数组 stones ,数组中的元素 严格递增 ,表示一条河中石头的位置。

一只青蛙一开始在第一块石头上,它想到达最后一块石头,然后回到第一块石头。同时每块石头 至多 到达 一次。

一次跳跃的 长度 是青蛙跳跃前和跳跃后所在两块石头之间的距离。

更正式的,如果青蛙从 stones[i] 跳到 stones[j] ,跳跃的长度为 |stones[i] - stones[j]| 。
一条路径的 代价 是这条路径里的 最大跳跃长度 。

请你返回这只青蛙的 最小代价 。

输入:stones = [0,2,5,6,7]
输出:5
解释:上图展示了一条最优路径。
这条路径的代价是 5 ,是这条路径中的最大跳跃长度。
无法得到一条代价小于 5 的路径,我们返回 5 。输入:stones = [0,3,9]
输出:9
解释:
青蛙可以直接跳到最后一块石头,然后跳回第一块石头。
在这条路径中,每次跳跃长度都是 9 。所以路径代价是 max(9, 9) = 9 。
这是可行路径中的最小代价。
class Solution {public int maxJump(int[] stones) {if(stones.length <= 2) return stones[1]-stones[0];int ans = stones[2]-stones[0];for(int i = 2; i < stones.length; i++) ans = Math.max(ans,stones[i]-stones[i-2]);return ans;}
}

本题小结:

(1)首先思考,是跳所有的石头产生的结果最小还是漏过一些石头产生的结果最小,很显然,把所有的石头都跳过,在中间相当于插值,所产生的结果才有可能是最小的,即min(跳所有的石头)<=min(漏过一些石头)

(2)一句话贪心证明:大于三个的一段距离都可以进行分解成更小的段

 

3. leetcode 403. 青蛙过河

一只青蛙想要过河。 假定河流被等分为若干个单元格,并且在每一个单元格内都有可能放有一块石子(也有可能没有)。 青蛙可以跳上石子,但是不可以跳入水中。

给你石子的位置列表 stones(用单元格序号 升序 表示), 请判定青蛙能否成功过河(即能否在最后一步跳至最后一块石子上)。开始时, 青蛙默认已站在第一块石子上,并可以假定它第一步只能跳跃 1 个单位(即只能从单元格 1 跳至单元格 2 )。

如果青蛙上一步跳跃了 k 个单位,那么它接下来的跳跃距离只能选择为 k - 1、k 或 k + 1 个单位。 另请注意,青蛙只能向前方(终点的方向)跳跃。

输入:stones = [0,1,3,5,6,8,12,17]
输出:true
解释:青蛙可以成功过河,按照如下方案跳跃:跳 1 个单位到第 2 块石子, 
然后跳 2 个单位到第 3 块石子, 接着 跳 2 个单位到第 4 块石子, 
然后跳 3 个单位到第 6 块石子, 跳 4 个单位到第 7 块石子, 最后,
跳 5 个单位到第 8 个石子(即最后一块石子)。输入:stones = [0,1,2,3,4,8,9,11]
输出:false
解释:这是因为第 5 和第 6 个石子之间的间距太大,没有可选的方案供青蛙跳跃过去。

DFS

class Solution {int[] stones;HashMap<Integer,Integer> map = new HashMap<>();boolean flag = false;public boolean canCross(int[] stones) {this.stones = stones;int len = stones.length;if(stones[1] >= 2) return false;for(int i =0; i < len; i++){map.put(stones[i],i);}dfs(1,1,len);return flag;}public void dfs(int index, int k, int len){if(index == len-1){flag = true;return;}if(k == 1){if(stones[index]+1 <= stones[len-1]){if(map.containsKey(stones[index]+1)){dfs(map.get(stones[index]+1),1,len);} }if(stones[index]+2 <= stones[len-1]){if(map.containsKey(stones[index]+2)){dfs(map.get(stones[index]+2),2,len);} }}else{if(stones[index]+k-1 <= stones[len-1]){if(map.containsKey(stones[index]+k-1)){dfs(map.get(stones[index]+k-1),k-1,len);} }if(stones[index]+k <= stones[len-1]){if(map.containsKey(stones[index]+k)){dfs(map.get(stones[index]+k),k,len);} }if(stones[index]+k+1 <= stones[len-1]){if(map.containsKey(stones[index]+k+1)){dfs(map.get(stones[index]+k+1),k+1,len);}}}   }
}

当然dfs是不能通过的

 

 记忆化搜索

class Solution {int[] stones;HashMap<Integer,Integer> map = new HashMap<>();boolean flag = false;boolean[][] memo;public boolean canCross(int[] stones) {this.stones = stones;int len = stones.length;if(stones[1] >= 2) return false;for(int i =0; i < len; i++){map.put(stones[i],i);}memo = new boolean[len][len+1];dfs(1,1,len);return flag;}public void dfs(int index, int k, int len){if(index == len-1){flag = true;return;}if(memo[index][k]) return;if(k == 1){if(map.containsKey(stones[index]+1)){dfs(map.get(stones[index]+1),1,len);} if(map.containsKey(stones[index]+2)){dfs(map.get(stones[index]+2),2,len);} }else{if(map.containsKey(stones[index]+k-1)){dfs(map.get(stones[index]+k-1),k-1,len);} if(map.containsKey(stones[index]+k)){dfs(map.get(stones[index]+k),k,len);} if(map.containsKey(stones[index]+k+1)){dfs(map.get(stones[index]+k+1),k+1,len);}}memo[index][k] = true;   }
}

实际上以上的解法可以看出很多代码是可以重复的,可以写成for循环,堆结果合并,并处理k=1的特殊情况。

Ref.[1]:

DFS

class Solution {Map<Integer, Integer> map = new HashMap<>();public boolean canCross(int[] ss) {int n = ss.length;// 将石子信息存入哈希表// 为了快速判断是否存在某块石子,以及快速查找某块石子所在下标for (int i = 0; i < n; i++) {map.put(ss[i], i);}// check first step// 根据题意,第一步是固定经过步长 1 到达第一块石子(下标为 1)if (!map.containsKey(1)) return false;return dfs(ss, ss.length, 1, 1);}/*** 判定是否能够跳到最后一块石子* @param ss 石子列表【不变】* @param n  石子列表长度【不变】* @param u  当前所在的石子的下标* @param k  上一次是经过多少步跳到当前位置的* @return 是否能跳到最后一块石子*/boolean dfs(int[] ss, int n, int u, int k) {if (u == n - 1) return true;for (int i = -1; i <= 1; i++) {// 如果是原地踏步的话,直接跳过if (k + i == 0) continue;// 下一步的石子理论编号int next = ss[u] + k + i;// 如果存在下一步的石子,则跳转到下一步石子,并 DFS 下去if (map.containsKey(next)) {boolean cur = dfs(ss, n, map.get(next), k + i);if (cur) return true;}}return false;}
}

 记忆化搜索

class Solution {Map<Integer, Integer> map = new HashMap<>();// int[][] cache = new int[2009][2009];Map<String, Boolean> cache = new HashMap<>();public boolean canCross(int[] ss) {int n = ss.length;for (int i = 0; i < n; i++) {map.put(ss[i], i);}// check first stepif (!map.containsKey(1)) return false;return dfs(ss, ss.length, 1, 1);}boolean dfs(int[] ss, int n, int u, int k) {String key = u + "_" + k;// if (cache[u][k] != 0) return cache[u][k] == 1;if (cache.containsKey(key)) return cache.get(key);if (u == n - 1) return true;for (int i = -1; i <= 1; i++) {if (k + i == 0) continue;int next = ss[u] + k + i;if (map.containsKey(next)) {boolean cur = dfs(ss, n, map.get(next), k + i);// cache[u][k] = cur ? 1 : -1;cache.put(key, cur);if (cur) return true;}}// cache[u][k] = -1;cache.put(key, false);return false;}
}

以上四种解答,两种写法,在本质上一样,枚举在一个位置可能的情况,进行递归,DFS都不能通过,使用记忆化搜索降低时间复杂度,走过的路不再走。 

动态规划 

class Solution {public boolean canCross(int[] ss) {int n = ss.length;// check first stepif (ss[1] != 1) return false;boolean[][] f = new boolean[n + 1][n + 1];f[1][1] = true;for (int i = 2; i < n; i++) {for (int j = 1; j < i; j++) {int k = ss[i] - ss[j];// 我们知道从位置 j 到位置 i 是需要步长为 k 的跳跃// 而从位置 j 发起的跳跃最多不超过 j + 1// 因为每次跳跃,下标至少增加 1,而步长最多增加 1 if (k <= j + 1) {f[i][k] = f[j][k - 1] || f[j][k] || f[j][k + 1];}}}for (int i = 1; i < n; i++) {if (f[n - 1][i]) return true;}return false;}
}

参考来源 Ref.

[1] leetcode 宫水三叶 【宫水三叶】一题四解 : 降低确定「记忆化容器大小」的思维难度


http://www.ppmy.cn/news/55131.html

相关文章

数组、链表专题

数组、链表专题 前缀和数组LeetCode 303. 区域和检索 - 数组不可变解题思路代码实现 LeetCode 304. 二维区域和检索 - 矩阵不可变解题思路代码实现 LeetCode 560. 和为 K 的子数组解题思路代码实现 差分数组LeetCode 303. 区域和检索 - 数组不可变解题思路代码实现 总结 不要纠…

Java 版Spring cloud 企业工程项目管理系统平台源码(三控:进度组织、质量安全、预算资金成本、二平台:招采、设计管理)

工程项目管理软件&#xff08;工程项目管理系统&#xff09;对建设工程项目管理组织建设、项目策划决策、规划设计、施工建设到竣工交付、总结评估、运维运营&#xff0c;全过程、全方位的对项目进行综合管理 工程项目各模块及其功能点清单 一、系统管理 1、数据字典&#…

【redis】redis分布式锁(二)可重入锁+设计模式

【redis】redis分布式锁&#xff08;二&#xff09;可重入锁 文章目录 【redis】redis分布式锁&#xff08;二&#xff09;可重入锁前言一、可重入锁&#xff08;又名递归锁&#xff09;1、说明&#xff1a;2、分开解释&#xff1a;3、可重入锁的种类隐式锁&#xff08;即synch…

duubo+zookeeper

1、Dubbo简介 1. Dubbo是什么&#xff1f; 高性能、轻量级、开源、基于java Dubbo 是阿里集团开源的远程服务调用的分布式框架&#xff08;告别Web Service模式中的WSDL&#xff0c;以服务者与消费者的方式在dubbo上注册&#xff09; 协议和序列化框架都可以插拔是及其鲜明…

scratch比大小 中国电子学会图形化编程 少儿编程 scratch编程等级考试三级真题和答案解析2023年3月

目录 scratch比大小 一、题目要求 1、准备工作 2、功能实现 二、案例分析

Hive ---- Hive 安装

Hive ---- Hive 安装 1. Hive安装地址2. Hive安装部署1. 安装Hive2. 启动并使用Hive 3. MySQL安装1. 安装MySQL2. 配置MySQL3. 卸载MySQL说明 4. 配置Hive元数据存储到MySQL1. 配置元数据到MySQL2. 验证元数据是否配置成功3. 查看MySQL中的元数据 5. Hive服务部署1. hiveserver…

访问学者出国申请可以分为哪几类?

申请出国访学的人越来越多&#xff0c;访学之所以受欢迎&#xff0c;对申请访学的人员来说&#xff0c;又有哪些收获&#xff1f;访问学者可以分为哪几类呢&#xff1f;按照访美经费来源&#xff0c;访问学者大致可分为三类&#xff0c;即公派访问学者、海外资助类和自费访问学…

经济回暖、兴趣电商升级,品牌在竞争白热化的市场中如何突围?| D3大会圆桌回顾

冬去春来&#xff0c;消费市场韧性回弹&#xff0c;消费趋势正处于“转折”和“跃升”的阶段。新的机遇和挑战也将伴随着新的思维、方法和模式&#xff0c;呈现出更多元的变化和创新&#xff1a;渠道虚实融合&#xff0c;内容为王&#xff0c;社会化媒体成为主战场等消费场景不…