一、题目
力扣原题:https://leetcode-cn.com/problems/jump-game/submissions/
二、深度优先搜索
class Solution {
private int[] nums;
private boolean[] visited;
public boolean canJump(int[] nums) {
if (null == nums || 0 == nums.length) {
return false;
}
// 标记已访问的节点
this.visited = new boolean[nums.length];
this.nums = nums;
return dfs(0);
}
private boolean dfs(int index) {
visited[index] = true;
// 成功跳出边界
if (index >= nums.length - 1) {
return true;
}
for (int step = index + 1; step <= index + nums[index]; step++) {
// 不同位置可跳跃的范围可能有重复,应该避免重复访问,否则会超时
if (visited[step]) {
continue;
}
if (dfs(step)) {
return true;
}
}
return false;
}
}
- 基本思路:深搜是比较直观的解题思路。对于当前位置index,可以跳跃的位置为[index + 1, index + nums[index]],可以据此写出深搜代码。值得注意的是,对于不同位置,可跳跃的范围可能有重叠,记录访问状态并进行剪枝可以大大提升运行效率。
- 时间复杂度:O(n)。搜索所有可能,通过标记位,每个节点至多访问一次。
- 空间复杂度:O(n)。即递归的深度,最差情况是每次只能跳一格。
三、记忆化跳跃
class Solution {
public boolean canJump(int[] nums) {
if (null == nums || 0 == nums.length) {
return false;
}
int fast = 0;
for (int i = 0; i < nums.length; i++) {
// 若遍历到最远点时,仍未跳出边界,说明无法跳出循环了
if (i > fast) {
return false;
}
// 更新可达的最远点
fast = Math.max(fast, i + nums[i]);
}
return true;
}
}
- 基本思路:分析题意,实际上我们只需要知道跳跃的最远距离能否逃出边界,因此仅需记录每个位置可以到达的最远点即可。
- 时间复杂度:O(n)
- 空间复杂度:O(1)
四、动态规划
class Solution {
public boolean canJump(int[] nums) {
if (null == nums || 0 == nums.length) {
return false;
}
// true:可以跳到当前位置,false:无法跳到当前位置
boolean[] dp = new boolean[nums.length];
// 初始状态
dp[0] = true;
for (int i = 1; i < nums.length; i++) {
for (int j = 0; j < i ; j++) {
// 状态转移方程:若存在j < i可达,并且最大可以跳到i之后,则i也是可达的
if (dp[j] && (nums[j] + j >= i)) {
dp[i] = true;
break;
}
}
}
return dp[nums.length - 1];
}
}
- 基本思路:dp[i]代表i位置的可达状态,true为可达,false为不可达。初始状态dp[0] = true。
- dp[i] = true,当且仅当存在j < i可达,并且最大可以跳到i位置之后;
- dp[i] = false,不满足上述条件时;
- 时间复杂度:O(n^2)。
- 空间复杂度:O(n)。记录所有位置的可达状态。
五、总结
- 探索类问题经常可以用搜索算法解决;
- 通过标记位记录访问状态,往往可以避免不必要的重复计算,大大降低搜索算法的时间复杂度;
- 动态规划算法的关键在于找到初始状态以及状态转移方程;