这道题目描述了一只青蛙想要过河的场景。河中有若干石头,青蛙需要从起点(第一个石头)跳到终点(最后一个石头)。青蛙的跳跃规则比较特殊:
举个例子,如果青蛙上次跳了3个单位,那么这次可以跳2、3或4个单位。我们需要判断青蛙是否能够成功到达终点。
这个问题可以抽象为一个状态转移问题。我们需要跟踪两个关键信息:
这种"当前状态由前序状态决定"的特点,正是动态规划适用的典型场景。我们可以用dp[i][k]来表示:是否能够以k的跳跃距离到达第i个石头。
有几个关键边界条件需要注意:
我们使用一个哈希表来记录每个石头位置可以到达的跳跃距离集合。具体步骤如下:
java复制class Solution {
public boolean canCross(int[] stones) {
int n = stones.length;
if (stones[1] != 1) return false; // 第一步必须跳1个单位
// dp[i] 表示到达第i个石头时,上一跳的距离集合
Map<Integer, Set<Integer>> dp = new HashMap<>();
for (int stone : stones) {
dp.put(stone, new HashSet<>());
}
dp.get(0).add(0); // 从起点开始,上一跳距离为0
for (int i = 0; i < n; i++) {
int curPos = stones[i];
Set<Integer> jumps = dp.get(curPos);
for (int k : jumps) {
// 尝试跳k-1, k, k+1的距离
for (int step = k - 1; step <= k + 1; step++) {
if (step <= 0) continue; // 步长必须为正
int nextPos = curPos + step;
if (dp.containsKey(nextPos)) {
dp.get(nextPos).add(step);
}
}
}
}
return !dp.get(stones[n - 1]).isEmpty();
}
}
广度优先搜索是另一种解决此问题的有效方法。我们可以将每个状态表示为(当前位置,上一跳距离),然后系统地探索所有可能的跳跃序列。
java复制class Solution {
public boolean canCross(int[] stones) {
int n = stones.length;
if (stones[1] != 1) return false;
// 用哈希表存储石头位置,便于快速查找
Map<Integer, Integer> stoneMap = new HashMap<>();
for (int i = 0; i < n; i++) {
stoneMap.put(stones[i], i);
}
// visited[i][k]表示是否以跳跃距离k到达过第i个石头
boolean[][] visited = new boolean[n][n + 1];
Queue<int[]> queue = new LinkedList<>();
queue.offer(new int[]{1, 1}); // {位置索引, 上一跳距离}
visited[1][1] = true;
while (!queue.isEmpty()) {
int[] cur = queue.poll();
int idx = cur[0];
int k = cur[1];
if (idx == n - 1) return true;
// 尝试跳k-1, k, k+1
for (int step = k - 1; step <= k + 1; step++) {
if (step <= 0) continue;
int nextPos = stones[idx] + step;
if (stoneMap.containsKey(nextPos)) {
int nextIdx = stoneMap.get(nextPos);
if (!visited[nextIdx][step]) {
visited[nextIdx][step] = true;
queue.offer(new int[]{nextIdx, step});
}
}
}
}
return false;
}
}
深度优先搜索配合记忆化也是一种直观的解决方案。我们从起点开始,尝试所有可能的跳跃序列,使用记忆化技术避免重复计算。
java复制class Solution {
public boolean canCross(int[] stones) {
int n = stones.length;
Map<Integer, Integer> stoneMap = new HashMap<>();
for (int i = 0; i < n; i++) {
stoneMap.put(stones[i], i);
}
// memo[i][k]表示从第i个石头,以跳跃距离k出发,是否能到达终点
Boolean[][] memo = new Boolean[n][n + 1];
return dfs(0, 0, stones, stoneMap, memo);
}
private boolean dfs(int idx, int k, int[] stones,
Map<Integer, Integer> stoneMap, Boolean[][] memo) {
if (idx == stones.length - 1) return true;
if (memo[idx][k] != null) return memo[idx][k];
boolean res = false;
// 尝试跳k-1, k, k+1
for (int step = Math.max(1, k - 1); step <= k + 1; step++) {
int nextPos = stones[idx] + step;
if (stoneMap.containsKey(nextPos)) {
int nextIdx = stoneMap.get(nextPos);
if (dfs(nextIdx, step, stones, stoneMap, memo)) {
res = true;
break;
}
}
}
memo[idx][k] = res;
return res;
}
}
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 动态规划 | O(n²) | O(n²) | 通用解法,代码简洁 |
| BFS | O(n²) | O(n²) | 适合求最短路径 |
| DFS+记忆化 | O(n²) | O(n²) | 思路直观,易理解 |
虽然这个问题看起来像是一个纯粹的算法练习,但它实际上模拟了许多现实世界中的场景:
理解这类问题的解决方法,有助于我们在面对类似约束条件下的决策问题时,能够快速建立有效的解决方案。