青蛙过河 图解题解
这道题到底在问什么
- 输入
- stones=[0,1,3,5,6,8,12,17]
- 输出
- true(存在一条可达路径)
- 输入
- stones=[0,1,2,3,4,8,9,11]
- 输出
- false(中间缺口过大)
最优解:一步一步想明白
- 3记住「jumps[x] 记到 x 的步长、每步尝试 k−1/k/k+1、落在石子就记下、碰到终点即 true」,下面逐石子套它。
- 4初始化:jumps[0]={0}。这个「0」是技巧:它让第一跳的步长 nk=0+1=1 恰好满足「首跳为 1」的规则。
- 5处理石子 0(紫):它的步长集合是 {0}。对每个 k,试着往前跳 k−1、k、k+1。
- 6从 0 用步长 1 跳到石子 1(绿)。把步长 1 记进 jumps[1],以后从 1 还能继续往前。
- 7处理石子 1(紫):它的步长集合是 {1}。对每个 k,试着往前跳 k−1、k、k+1。
- 8从 1 用步长 1 会落到 2,但那里没有石子,这一跳不行。
- 9从 1 用步长 2 跳到石子 3(绿)。把步长 2 记进 jumps[3],以后从 3 还能继续往前。
- 10处理石子 3(紫):它的步长集合是 {2}。对每个 k,试着往前跳 k−1、k、k+1。
- 11从 3 用步长 1 会落到 4,但那里没有石子,这一跳不行。
- 12从 3 用步长 2 跳到石子 5(绿)。把步长 2 记进 jumps[5],以后从 5 还能继续往前。
- 13从 3 用步长 3 跳到石子 6(绿)。把步长 3 记进 jumps[6],以后从 6 还能继续往前。
- 14处理石子 5(紫):它的步长集合是 {2}。对每个 k,试着往前跳 k−1、k、k+1。
- 15从 5 用步长 1 跳到石子 6(绿)。把步长 1 记进 jumps[6],以后从 6 还能继续往前。
- 16从 5 用步长 2 会落到 7,但那里没有石子,这一跳不行。
- 17从 5 用步长 3 跳到石子 8(绿)。把步长 3 记进 jumps[8],以后从 8 还能继续往前。
- 18处理石子 6(紫):它的步长集合是 {1, 3}。对每个 k,试着往前跳 k−1、k、k+1。
- 19从 6 用步长 1 会落到 7,但那里没有石子,这一跳不行。
- 20从 6 用步长 2 跳到石子 8(绿)。把步长 2 记进 jumps[8],以后从 8 还能继续往前。
- 21从 6 用步长 2 跳到石子 8(绿)。它的集合里已有 2。
- 22从 6 用步长 3 会落到 9,但那里没有石子,这一跳不行。
- 23从 6 用步长 4 会落到 10,但那里没有石子,这一跳不行。
- 24处理石子 8(紫):它的步长集合是 {2, 3}。对每个 k,试着往前跳 k−1、k、k+1。
- 25从 8 用步长 1 会落到 9,但那里没有石子,这一跳不行。
- 26从 8 用步长 2 会落到 10,但那里没有石子,这一跳不行。
- 27从 8 用步长 3 会落到 11,但那里没有石子,这一跳不行。
- 28从 8 用步长 2 会落到 10,但那里没有石子,这一跳不行。
- 29从 8 用步长 3 会落到 11,但那里没有石子,这一跳不行。
- 30从 8 用步长 4 跳到石子 12(绿)。把步长 4 记进 jumps[12],以后从 12 还能继续往前。
- 31处理石子 12(紫):它的步长集合是 {4}。对每个 k,试着往前跳 k−1、k、k+1。
- 32从 12 用步长 3 会落到 15,但那里没有石子,这一跳不行。
- 33从 12 用步长 4 会落到 16,但那里没有石子,这一跳不行。
- 34从 12 用步长 5 正好跳到终点 17!青蛙过河成功,直接返回 true。
- 35回放一条可行路径:0→1→3→5→8→12→17,每一跳的步长都满足「上一跳 ±1」的规则,最终落在终点 17。答案 true。
⚠️ 容易写错的地方
✗ 错:把首跳也允许 0 或 k 任意
✓ 对:首跳固定 1,用 jumps[0]={0} 巧妙实现
jumps[0] 放步长 0,使首跳 nk∈{−1,0,1} 里只有 1 合法(nk>0),自然强制首跳为 1
✗ 错:nk 允许为 0 或负
✓ 对:只接受 nk>0 的跳
步长必须为正(青蛙得往前),k=1 时 k−1=0 不是合法跳,要过滤掉
✗ 错:边遍历 jumps[x] 边往里加
✓ 对:先对 jumps[x] 取快照再遍历
处理 x 时只会往 x 之后的石子加步长、不改 jumps[x] 本身;但稳妥起见拷快照可避免某些语言的并发修改异常
完整代码(Python / C++ / Java)
Python
from typing import List
from collections import defaultdict
class Solution:
def canCross(self, stones: List[int]) -> bool:
stone_set = set(stones)
jumps = defaultdict(set)
jumps[0].add(0)
last = stones[-1]
for x in stones:
for k in list(jumps[x]):
for nk in (k - 1, k, k + 1):
if nk > 0 and x + nk in stone_set:
if x + nk == last:
return True
jumps[x + nk].add(nk)
return last == 0C++
#include <unordered_map>
#include <unordered_set>
#include <vector>
using namespace std;
class Solution {
public:
bool canCross(vector<int>& stones) {
unordered_set<int> pos(stones.begin(), stones.end());
unordered_map<int, unordered_set<int>> jumps;
jumps[0].insert(0);
int last = stones.back();
for (int x : stones) {
auto cur = jumps[x];
for (int k : cur) for (int nk : {k - 1, k, k + 1}) if (nk > 0 && pos.count(x + nk)) {
if (x + nk == last) return true;
jumps[x + nk].insert(nk);
}
}
return false;
}
};Java
import java.util.*;
class Solution {
public boolean canCross(int[] stones) {
Set<Integer> pos = new HashSet<>();
for (int x : stones) pos.add(x);
Map<Integer, Set<Integer>> jumps = new HashMap<>();
jumps.computeIfAbsent(0, k -> new HashSet<>()).add(0);
int last = stones[stones.length - 1];
for (int x : stones) {
for (int k : new HashSet<>(jumps.getOrDefault(x, Collections.emptySet()))) {
for (int nk = k - 1; nk <= k + 1; nk++) if (nk > 0 && pos.contains(x + nk)) {
if (x + nk == last) return true;
jumps.computeIfAbsent(x + nk, t -> new HashSet<>()).add(nk);
}
}
}
return false;
}
}复杂度
时间
O(n²)
n 是石子数。每块石子的步长集合最多 O(n) 个,每个步长试 3 个方向 O(1) 判石子;总体 O(n²)
空间
O(n²)
jumps 表最坏每块石子存 O(n) 个步长,合计 O(n²);石子集合 O(n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 青蛙过河 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
能不能用记忆化 DFS(从位置 + 上一步长 出发)来解?+
可以,而且很自然。定义 dfs(下标 i, 上一跳步长 k):从石子 i 出发、上一跳为 k,能否到终点。枚举下一跳 nk∈{k−1,k,k+1},用二分或哈希找 x+nk 是不是石子、是第几块,递归下去;用 memo[(i,k)] 缓存避免重复。和本文的正推 DP 等价,状态都是「(石子, 上一跳步长)」,复杂度同为 O(n²)。
为什么本题不能用「能否到达每块石子」这种一维布尔 DP?+
因为「能不能跳到下一块」不只取决于「站在哪」,还取决于「上一跳跨了多少」(它决定本次步长范围)。一维 dp[i] 丢掉了步长信息,无法判断后续能不能继续跳。所以状态必须是二维的「(石子, 上一跳步长)」,本文用 jumps[x] 这个集合正是在为每块石子记录所有可能的步长。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 青蛙过河 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。