这个页面在做什么。上方是调用树,下方是调用栈,右侧是对应的 Java 代码。按单行推进时,高亮行是当前执行位置,栈帧从下往上堆叠,栈顶那一帧白框标出。调用栈是 JVM 替你维护的,代码里看不到它——这里把它画出来。
递归四步。契约:先定死这个函数对任意输入返回什么。基线:最小的输入直接给答案,不再递归。分解:拆成同形状的更小问题。合并:用子问题的答案拼出自己的答案。四步齐了,函数就是对的。
递归的信任。写第 4 行时不要在脑子里展开 maxDepth(root.left),就当它已按契约返回了左子树的深度。展开是 JVM 的事。
执行顺序不是「先冲到最深再一路返回」。每一层都在做同一件事:下去 → 回来 → 继续下一句。第 4 行返回后停在第 5 行,这时才轮到右子树——右边的整棵树在此之前一步都没动。
栈的位置。BFS 那页里队列是你自己声明的 ArrayDeque;这里的调用栈同样是个容器,只是 LIFO,且由 JVM 维护。栈帧里存的就是每层的 root、L、R。递归深度直接等于栈高,栈溢出就是这里放不下了。
关键的一步。某一帧触底返回时它收起消失,返回值落进紧邻下方那一帧的 L 或 R(黄色闪一下)。整个递归的信息只沿这一条路径回流,别处没有通道。
记忆化。斐波那契不加缓存时 fib(n-1) 与 fib(n-2) 的子树大量重叠,调用次数 O(2ⁿ);缓存让每个 n 只算一次,降到 O(n)。哨兵值不能用 0——fib(0) 的真实答案就是 0,用 0 判空会把它当成未命中反复重算,这里用 -1。
快捷键。空格 播放 / 暂停,→ 单行推进,R 重置。焦点在滑块上时按键归滑块。树递归模式下点击任意节点可把它设为新的根。