LeetCode第55题"跳跃游戏"是一个经典的算法问题,考察的是贪心算法的应用能力。题目给定一个非负整数数组nums,数组中的每个元素代表在该位置可以跳跃的最大长度。我们需要判断是否能够从数组的第一个位置到达最后一个位置。
这个问题的实际意义非常广泛。比如在游戏开发中,可以用来验证玩家是否能够通过所有平台到达终点;在网络路由中,可以判断数据包是否能够通过一系列路由器到达目的地;在机器人路径规划中,可以验证机器人是否能够从起点移动到目标位置。
最直观的解法是使用递归回溯的方法,尝试所有可能的跳跃路径:
java复制public boolean canJump(int[] nums) {
return backtrack(nums, 0);
}
private boolean backtrack(int[] nums, int position) {
if (position == nums.length - 1) {
return true;
}
int furthestJump = Math.min(position + nums[position], nums.length - 1);
for (int nextPosition = position + 1; nextPosition <= furthestJump; nextPosition++) {
if (backtrack(nums, nextPosition)) {
return true;
}
}
return false;
}
这种解法的时间复杂度是O(2^n),对于n=10^4的数据规模来说完全不可行。即使使用记忆化优化,空间复杂度也会很高。
更高效的解法是使用贪心算法。核心思想是维护一个变量rightmost,表示当前能够到达的最远位置。我们遍历数组中的每个位置i:
这种解法的时间复杂度是O(n),空间复杂度是O(1),非常高效。
java复制public boolean canJump(int[] nums) {
int n = nums.length;
int rightmost = 0;
for (int i = 0; i < n; i++) {
if (i > rightmost) {
return false;
}
rightmost = Math.max(rightmost, i + nums[i]);
if (rightmost >= n - 1) {
return true;
}
}
return false;
}
我们可以做一些小的优化:
java复制public boolean canJump(int[] nums) {
if (nums.length == 1) return true;
int rightmost = 0;
for (int i = 0; i < nums.length - 1; i++) {
if (i > rightmost) return false;
rightmost = Math.max(rightmost, i + nums[i]);
if (rightmost >= nums.length - 1) return true;
}
return false;
}
虽然贪心算法是最优解,但为了完整理解问题,我们也可以使用动态规划:
java复制public boolean canJump(int[] nums) {
int n = nums.length;
boolean[] dp = new boolean[n];
dp[0] = true;
for (int i = 0; i < n; i++) {
if (!dp[i]) continue;
int maxJump = Math.min(i + nums[i], n - 1);
for (int j = i + 1; j <= maxJump; j++) {
dp[j] = true;
if (j == n - 1) return true;
}
}
return dp[n - 1];
}
动态规划的时间复杂度是O(n^2),空间复杂度是O(n),不如贪心算法高效。
贪心算法的正确性可以通过数学归纳法证明:
在实际编码中,我们需要考虑以下边界情况:
因为我们只关心是否可达,而不关心如何到达。贪心算法通过维护最远可达位置,确保了如果终点可达,我们一定能检测到。具体的路径信息是冗余的。
不会。我们在更新rightmost时,实际上不需要担心越界,因为:
这就变成了LeetCode 45题"跳跃游戏 II"。需要使用BFS或贪心的变种:
java复制public int jump(int[] nums) {
int jumps = 0, currentEnd = 0, farthest = 0;
for (int i = 0; i < nums.length - 1; i++) {
farthest = Math.max(farthest, i + nums[i]);
if (i == currentEnd) {
jumps++;
currentEnd = farthest;
}
}
return jumps;
}
在平台跳跃游戏中,可以用这个算法验证关卡是否可通关:
java复制public class LevelValidator {
public boolean isLevelCompletable(int[] jumpCapabilities) {
return canJump(jumpCapabilities);
}
}
可以用来验证数据包是否能够通过一系列路由器到达目的地,每个路由器的跳数限制对应数组中的元素。
在云计算资源调度中,可以验证任务是否能在给定的资源约束下完成,每个时间点的可用资源对应数组中的元素。
跳跃游戏问题展示了贪心算法的强大之处 - 用简单的逻辑解决看似复杂的问题。关键点在于:
这个算法的时间复杂度是O(n),空间复杂度是O(1),是最优解法。理解这个问题的解法不仅有助于通过面试,更能培养对算法本质的理解能力。