Jump Games在LeetCode中是一个系列的问题,涉及动态规划、贪心等策略,非常有意思,尤其是昨天周赛再次遇到了这个系列的新题,在此复盘、总结。
Jump Game
输入一个数组,从下标0开始,数组中每个数字表示你能跳跃的最大距离,问能否到达终点。
这道题是能否到达终点,返回的是一个布尔值,所以不用关心到达终点的方式以及相关数量。假设有i,它可以跳跃的距离是d,那么在距离(i+1, i+d)之间都是可行进的范围,我们需要从这里面选出一步来往下走,对于下一步也重复上一步的操作,直到终点。但是这样做的话时间复杂度会到O(n^2),提交肯定会TLE。
dp
动态规划,用数组维护一个跳力值,只要到终点还有跳力(不为负)就说明是可行的。如何去写状态转移方程呢?对于一个位置i,只能从前n步中过来,但是这是不确定的,所以我们可以一步一步地往前走,同时维护最大跳力即可,所以回到刚才的位置i,他是从i-1过来的,那么他所剩的跳力可以从上一步中获取,到目前的i时减去一(i-1到i要耗费一个跳力)。对于i-1,有两种选择,一种是维护的跳力dp,另一种是i-1位置时获取的跳力nums,取一个最大值就行。dp中存储的就是跳力值了。
1 | public boolean canReach(int[] nums){ |
greedy
可以对上面的dp做化简,我们只关心能否到达终点,一个简单的布尔值,不关心最终能跳多远,也就是跳力,所以可以只保留一个最远的值,判断他是否到比终点大就可,用到了贪心的思想。
1 | public boolean canReach(int[] nums){ |
Jump Game II
同样是给一个数组,从下标0开始,每个数字表示你能跳跃的最大距离,区别是,在保证总能到达终点的情况下,返回耗费最少的跳跃次数。
既然题目都保证了是终点可达的,那么是不是可以每次都选择跳的最远的那个?具体怎么选?其实不然,题目要求返回最少的次数,没有让详细返回跳的方案,所以,在一个能跳跃的区间内,总有一个符合条件的点,循环往复总会到达终点,我们只需要记录“循环”多少次不就行了。有三个变量:jump、curEnd、curFarthest,对于每一步都有一个区间范围,范围的末端就是curEnd,每个范围都会产生一个能到达的最远距离,我们以curEnd和curFarthest的最大值作为末尾标志,一旦区间跑完,就加一次jump,这样就做到了不关心具体在哪个点jump,而只关心次数了。
1 | public boolean minJumpTimes(int[] nums){ |
Jump Game III
给定一个数组和一个起始位置start,对于每个点,只能走i-start或i+start两个位置,问能否访问到数组中任意0的位置。
这种题实际上也是属于模拟,根据题目中的要求来做就好了,目前至少有两种方式,一种是递归地遍历(或者说深度优先),同时带上备忘录来标记访问过的点,另一种是广度优先,因为一个点可以带出来两个访问的方向,而且是顺序进行的。
dfs
回溯,总是可以穷举出来的,每个点上都有两种方式可选,同一个点可能会被访问多次,我们也需要做一个标记来表示他被访问过,不能到达的话不必重复访问。
1 | public boolean canReach(int][] nums, int start){ |
bfs
……
JumpGame VII
给一个数组和一个minJump以及一个maxJump,其中只包含0或1,下标0处为0,从下标0处出发,每次跳跃的范围是(i+minJump, i+maxJump)且只能跳落到其中的0处,问能否跳跃到终点。
可以怎么做?如果按照题目中的要求,在一个范围内找寻0来作为下一个起跳点,那么对于长度为n的数组,即便是使用dp来保存状态,时间复杂度也将会是O(n^2)。参考上面类似的思想, 他只询问能到到达终点,并不关心具体的路径,所以可以在范围的地方着手优化。