这是我参与8月更文挑战的第18天,活动详情查看:8月更文挑战
55. 跳跃游戏
递归
这个问题很容易我们就容易想到递归,形式上我们考虑如下的问题:
-
在第i阶我们可以选择走1-j步,这是我们递归的内容
-
到第i阶,如果到达或者超过了最大的台阶数,就说明可以走到,这是我们的正确边界条件。
- 至于走不到的情况?那就是1-j步里都走不到呗。
那根据这些推论,很容易就能构造出我们需要的递归方式的结果:
public boolean canJump(int[] nums) {
return t(nums,0);
}
public boolean t(int[] nums,int curF){
if(curF>=nums.length-1) return true;
for (int i = 1; i < nums[curF]; i++) {
if(t(nums,curF+i)){
return true;
}
}
return false;
}
结果: 超时
不出意料我们超时了,原因其实很简单:我们列举了过多的情况,实际上很多情况都重复了。
DP数组
既然超时了,那么当然我们考虑使用dp数组,来优化上面的问题。
我们注意到一个特性:
- 在第j个台阶,是否能走到最后,是确定的。
因此我们可以使用布尔数组来记录结果,并且推知:
dp[i] = dp[i+1] | ... | dp[i+j] , j<=nums[i]
因此我们从后往前推,优化上面的算法如下:
boolean[] dp = new boolean[nums.length];
dp[nums.length-1] = true;
for (int i = dp.length - 2; i >= 0; i--) {
for (int i1 = 0; i1 <=nums[i] && !dp[i] ; i1++) {
if(i+i1>nums.length-1){
dp[i] = true;
break;
}
dp[i] = dp[i+i1];
}
}
return dp[0];
执行用时:209 ms, 在所有 Java 提交中击败了11.37%的用户
内存消耗:39.6 MB, 在所有 Java 提交中击败了78.73%的用户
贪心
从结果上看我们还是有些慢,原因跟上面相似:
- 我们考虑了过多的结果,实际上我们只需要知道一个结果就可以了。
在这里考虑使用动态规划的特殊情况:贪心算法。
我们知道:
- 在第j台阶,最多走到j + nums[j]个台阶。
我们需要考虑的事情,其实是:
- 从第0台阶开始,我们最多能走到哪个台阶?
那么其实我们只要记录在每次枚举范围内,我们最远可以走到哪里就可以了。
同时为了额外的情况,我们需要判断:如果当前枚举的位置已经超过了最远的距离,那么我们就不应该再往下而是退出。
public static boolean canJump1(int[] nums) {
int maxStep = 0;
for (int i = 0; i < nums.length; i++) {
if(i>maxStep) break;
maxStep = Math.max(maxStep,i+nums[i]);
}
return maxStep>=nums.length-1;
}
执行用时:3 ms, 在所有 Java 提交中击败了51.70%的用户
内存消耗:39.7 MB, 在所有 Java 提交中击败了69.69%的用户