2021.05.05青蛙过河
2021.05.05青蛙过河
(题目来源:https://leetcode-cn.com/problems/frog-jump/)
题目描述
一只青蛙想要过河。 假定河流被等分为若干个单元格,并且在每一个单元格内都有可能放有一块石子(也有可能没有)。 青蛙可以跳上石子,但是不可以跳入水中。
给你石子的位置列表 stones(用单元格序号 升序 表示), 请判定青蛙能否成功过河(即能否在最后一步跳至最后一块石子上)。
开始时, 青蛙默认已站在第一块石子上,并可以假定它第一步只能跳跃一个单位(即只能从单元格 1 跳至单元格 2 )。
如果青蛙上一步跳跃了 k 个单位,那么它接下来的跳跃距离只能选择为 k - 1、k 或 k + 1 个单位。 另请注意,青蛙只能向前方(终点的方向)跳跃。
思路
【TLE】思路1:dfs
- 通过遍历所有跳石头的情况,直到跳到最后一块石头,返回true。
- 由于是盲目地跳石头,会出现多次通过k步跳到第i块石头的状态
【TLE】思路2:动态规划
- 为了防止盲目,记下之前的一些子状态。
- 定义状态:dp[i][j] :能通过第i块石头跳到第j块石头。跳的步数为arr[j]-arr[i]。
- 状态转移方程:
若满足:
k = = ( l a s J u m p ) = = > i = = ( c u r J u m p ) = = > j k ==(lasJump)==> i ==(curJump)==> j k==(lasJump)==>i==(curJump)==>j
且:
l a s = = c u r ∣ ∣ l a s = = c u r − 1 ∣ ∣ l a s = = c u r + 1 las == cur || las == cur-1 || las == cur+1 las==cur∣∣las==cur−1∣∣las==cur+1
则:
d p [ i ] [ j ] = d p [ i ] [ j ] ∣ ∣ d p [ k ] [ i ] ; dp[i][j] = dp[i][j] || dp[k][i]; dp[i][j]=dp[i][j]∣∣dp[k][i];
但是,由于找到前一块石头需要遍历,所以导致TML。
【优化】思路2:动态规划+二分查找
查找前前个石头时,采用二分查找
优化后代码如下:
public boolean canCross(int[] arr) {int n = arr.length;if(n == 2) return arr[1]-arr[0] == 1;//定义状态://dp[i][j] == true:第i到第j个是可以到达的,可以计算步数k = arr[j]-arr[i] (i < j)//k ==(arr[i]-arr[k])==> i ==(arr[j]-arr[i])==> j boolean[][] dp = new boolean[n][n]; dp[0][0] = true; dp[0][1] = (arr[1]-arr[0] == 1);for(int j = 1; j < n; j++) {for(int i = j-1; i >= 1; i--) {int cur = arr[j]-arr[i];//上一步是 cur / cur-1 / cur+1if(cur-1>i) break; //优化,提前跳出该情况//上一步的所有可能步长for(int las = cur-1; las <= cur+1; las++) {int ind = Arrays.binarySearch(arr, 0, i, arr[i]-las);if(ind >= 0) dp[i][j] = dp[i][j] || dp[ind][i];}if(j == n-1 && dp[i][j]) return true; }}return false;}
思路3:动态规划
- 为了找到更好的状态转移方程以防止需要循环查找前前个石头,需要改变状态定义。
- 定义状态 dp[i][k] : 能否通过跳k步跳到第i块石头。(由于步长每次最多递增1步的限制,所以状态矩阵的第二维也不会超过n。
- 状态转移方程:
(1)状态转移描述
假设从第j块石头跳到第i块石头跳了k步,但是k必须满足“跳到第j块石头是通过跳了k或k-1或k+1步”
(2)将描述通过方程表达dp[i][k] = dp[j][k] || dp[j][k-1] || dp[j][k+1]
代码如下:
public boolean canCross(int[] stones) {int n = stones.length;//定义状态://dp[i][j] == true:到达第i个单元,跳了k步,boolean[][] dp = new boolean[n][n]; dp[0][0] = true;for(int i = 1; i < n; i++) {for(int j = i-1; j >= 0; j--) {//跳的步数int k = stones[i] - stones[j]; //上一次跳的最少步数为k-1,如果最少步数都超过了j,则说明不符合if(k-1 > j) break; dp[i][k] = dp[j][k-1] || dp[j][k] || dp[j][k+1];//可以在循环中判断,也可以在dp矩阵初始化完成后再判断。if(i == n-1 && dp[i][k] == true) return true;}}return false;}
思路4:记忆化搜索+Map
- 由于Map查找效率高,可以完成以下操作:
(1)存储历史状态,查找当前处理的情况之前有没有已经处理过。
(2)查找一个位置是否有石头,即一个数x是否在stones[]数组中。 - 进行dfs搜索。
代码:
//<石头的位置, 数组中下标>Map<Integer, Integer> map ; //用于存放石头的位置,方便之后查找Map<Integer, Boolean> cache; //存储的是之前遍历的结果public boolean canCross(int[] stones) {map = new HashMap<>();cache =new HashMap<>();for (int i = 0; i < stones.length; i++) {map.put(stones[i],i);}//默认 第一位置只能跳1个单位return dfs(stones,0,1);}private boolean dfs(int[] stones,int start,int k){// key的映射函数Integer key = stones[start] * 2000 * 2000 + k;//如果之前已经处理过该状态if(null != cache.get(key)) return cache.get(key);//查找下一个位置是否存在int index = map.getOrDefault(stones[start] + k,-1);//跳不到 || 回头跳 || 原地跳if(index == -1 || index <= start) {cache.put(key, false);return false;}// 跳到最后if(index == stones.length-1) {cache.put(key, true);return true;}// 跳k k-1 k+1 只要任意一个跳到就可以boolean b = dfs(stones, index, k + 1) || dfs(stones, index, k) || dfs(stones, index, k - 1);cache.put(key,b);return b;}
2021.5.19题外话:
再看此种解法,产生了以下问题:
- 为何需要记录一个key(start, step)的映射?
答:在定义状态时,我们定义决定一个状态共有两个参数,即当前的位置i和跳到i块石头的步数step,所以为了避免重复遍历状态,我们需要建立key(start, step)的映射函数。
即对于状态(i , k),如果其不能遍历到终点,我们回溯时返回false,其他分支遍历过程中再次遇到此状态,可以直接返回false。 - 会不会根本不会出现重复的“从j跳k步到i”的状态呢,只会出现“重复遍历到位置i”。
答:错。
若按照上述依据写代码:
仍报超时错误。故再思考。
参考别人的图解,以[1,2,3,4,5,999]为例,见图:
在这里插入图片描述](https://img-blog.csdnimg.cn/d999be69fa884b94bc42275fbd22dd4b.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWmFja196Y196Yw==,size_17,color_FFFFFF,t_70,g_se,x_16)
2021.05.05青蛙过河相关推荐
- 2021年05月软件设计师真题透析
2021年05月软件设计师上午真题及答案解析 1. 在 CPU 中,用( )给出将要执行的下一条指令在内存中的地址. A.程序计数器 B.指令寄存器 C.主存地址寄存器 D.状态条件寄存器 答案:A, ...
- 【每日一知】带你走近5nm芯片 (2021.02.05 )
[每日一知]带你走近5nm芯片 (2021.02.05 ) [每日一知]带你走近5nm芯片 (2021.02.05 ) ==一.简介== ==二.优势== ==三.现状== ============= ...
- html5青蛙过河,[推荐]===PS4上的本地多人游戏推荐心得===家庭聚会,欢乐时光 (持续更新)...
本帖最后由 everyer 于 2018-11-25 22:22 编辑 更新: =============经典派对小游戏合集============= Arcade Islands: Volume O ...
- java青蛙过河打字_趣味算法——青蛙过河(JAVA)
青蛙过河是一个非常有趣的智力游戏,其大意如下: 一条河之间有若干个石块间隔,有两队青蛙在过河,每队有3只青蛙,这些青蛙只能向前移动,不能向后移动,且一次只能有一只青蛙向前移动.在移动过程中,青蛙可以向 ...
- AI公开课:18.05.05 施尧耘(阿里云量子技术CS)—清华AI第四讲之《人工智能与量子计算》Quantum课堂笔记——带你了解量子计算
AI公开课:18.05.05 施尧耘(阿里云量子技术CS)-清华AI第四讲之<人工智能与量子计算>Quantum课堂笔记--带你了解量子计算 导读 清华大学"人工智能前沿与产业趋 ...
- 两个各四只青蛙过河java_趣味算法——青蛙过河(JAVA)
/*** 青蛙过河 *@authorrubekid **/ public classRiverFrog {public static final int LEFT_FROG = -1;public s ...
- nyoj-619 青蛙过河
题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=619 题目大意:就是有一条宽为L的河,河上有n个石头,每个石头与河对岸的距离为a[i],然 ...
- 没有终结点在侦听可以接受消息的_【大卫聊股】2019.05.05 周末重要消息分析及下周一走势预判...
2019.05.05 重要消息: 1. [发改委:适当降低新增分布式光伏发电补贴标准] 显然是利空光伏股. 2. [电报|7只科创基金最终募资金额出炉 首募合计超1200亿]多家基金公司发布公告,科创 ...
- P1244 青蛙过河
P1244 青蛙过河 题目描述 有一条河,左边一个石墩(A区)上有编号为1,2,3,4,-,n的n只青蛙,河中有k个荷叶(C区),还有h个石墩(D区),右边有一个石墩(B区),如下图所示.n只青蛙要过 ...
最新文章
- Linux系统守护进程详解
- 二、使用rails3.0自带的数据检查功能检查输入数据
- YOLO系列阅读(一) YOLOv1原文阅读:You Only Look Once: Unified, Real-Time Object Detection
- 个人电脑详细的安全设置方法之一
- boost::math::statistics相关用法的测试程序
- Java开源权限管理中间件
- 线性规划的matlab实现
- 如何在 ASP.NET Core 中使用 NLog 的高级特性
- react回调函数_React中的回调中自动绑定ES6类函数
- 在Nginx/Tengine服务器上安装SSL证书
- matlab fopen wt,matlab的fopen和fprintf
- js 设置style属性
- sockets php,PHP: Sockets - Manual
- JavaScript中一些常用的方法整理
- Rust 从入门到精通12-集合
- sublime3dsmax - Sublime Text Send To 3ds Max 解决中文路径问题
- python-微信公众个性二维码生成-生成自己名片二维码-链接二维码【超酷】
- Sqlite锁与事务
- gamemaker: studio html5,HTML5 Game Development with Gamemaker
- layui table 改变列表字体颜色
热门文章
- Ubuntu 设置默认播放器、浏览器、图片查看器
- keystore导出p12,cer,crt,.key.pem证书文件格式
- Java毕设项目奥利给共享自习室系统(java+VUE+Mybatis+Maven+Mysql)
- 985复旦大学,软件工程学硕停止招生!
- 【项目经验】最新最全ElasticSearch操作详解
- h桥控制电机刹车_51单片机H桥电路控制电机正反转和PWM调速
- 直通滤波(PassThrough 过滤器)
- (八)QRJDC正式版/QQ扫码登录直达青龙/Docker上的部署教程/很干很干/没水喝【2020年5月1日】
- c语言顺序线性表的实现
- 用友NC 财务核算账簿启用时 提示选中的科目表不是当前集团的账簿类型对应科目体系下的政策性科目表或其派生的科目表