从一个节点到另一个节点每一步的方向 图解题解
这道题到底在问什么
- 输入
- root=[50,30,80,20,40,·,·,15], start=15, dest=40
- 输出
- "UUR"(15→20→30→40)
最优解:一步一步想明白
- 3记住「求两条根到点路径 → 砍掉公共前缀(到 LCA)→ start 剩余转 U,接 dest 剩余」,下面逐帧套它。
- 4start=15 dest=40先把起点 15(红)和终点 40(绿)标出来。接下来分别求「根到 15」和「根到 40」的方向串。
- 5找 start 路径 = 空找 start:DFS 来到节点 50,还不是目标,先试着往左下走。
- 6找 start 路径 = L先猜左边:在 50 记下 L,下探它的左孩子 30。
- 7找 start 路径 = L找 start:DFS 来到节点 30,还不是目标,先试着往左下走。
- 8找 start 路径 = LL先猜左边:在 30 记下 L,下探它的左孩子 20。
- 9找 start 路径 = LL找 start:DFS 来到节点 20,还不是目标,先试着往左下走。
- 10找 start 路径 = LLL先猜左边:在 20 记下 L,下探它的左孩子 15。
- 11找 start 路径 = LLL走到节点 15,正是要找的 起点!
- 12找 start = LLL根到 15 的方向串定下来:LLL(从根一路往下读)。
- 13找 dest 路径 = 空找 dest:DFS 来到节点 50,还不是目标,先试着往左下走。
- 14找 dest 路径 = L先猜左边:在 50 记下 L,下探它的左孩子 30。
- 15找 dest 路径 = L找 dest:DFS 来到节点 30,还不是目标,先试着往左下走。
- 16找 dest 路径 = LL先猜左边:在 30 记下 L,下探它的左孩子 20。
- 17找 dest 路径 = LL找 dest:DFS 来到节点 20,还不是目标,先试着往左下走。
- 18找 dest 路径 = LLL先猜左边:在 20 记下 L,下探它的左孩子 15。
- 19找 dest 路径 = LLL找 dest:DFS 来到节点 15,还不是目标,先试着往左下走。
- 20找 dest 路径 = LLLL15 没有左孩子,这个 L 走不通,马上改方向。
- 21找 dest 路径 = LLLR15 的左子树里没有目标,回到 15、把那一步从 L 改成 R,可这边也是空的。
- 22找 dest 路径 = LLL15 这棵子树里没有 40,把它从路径里撤掉(回溯),回到上一层换别的方向。
- 23找 dest 路径 = LLR20 的左子树里没有目标,回到 20、把那一步从 L 改成 R,可这边也是空的。
- 24找 dest 路径 = LL20 这棵子树里没有 40,把它从路径里撤掉(回溯),回到上一层换别的方向。
- 25找 dest 路径 = LR30 的左子树里没有目标,回到 30、把那一步从 L 改成 R,改探右孩子 40。
- 26找 dest 路径 = LR走到节点 40,正是要找的 终点!
- 27找 dest = LR根到 40 的方向串定下来:LR(从根一路往下读)。
- 28a[0]=L b[0]=L 相同逐位比 a、b:第 1 位都是 L,是公共的,顺着它走到节点 30。公共段还在往下延伸。
- 29公共前缀长度 = 1,LCA = 30第 2 位:a 是 L、b 是 R,不一样了,公共前缀到此为止。公共段的终点节点 30 就是 start 和 dest 的最近公共祖先 LCA,从这里开始两条路就分开了。
- 30还剩 2 步 → UUa 砍掉公共前缀后还剩 2 步(原本是往下的 LL)。现在要反过来从 15 往上爬到 LCA 30,一共 2 步,每步记一个 U,得到 UU。
- 31下行段 = Rb 砍掉公共前缀后剩下「R」,这正是从 LCA 30 往下走到 40 的方向,照原样接在 U 段后面就行。
- 32答案 = UUR把两段接起来:UU 加上 R,完整路线 15→…→30→…→40 的方向串就是 UUR。
⚠️ 容易写错的地方
✗ 错:start 那段也照抄 L/R
✓ 对:start 去掉公共前缀后的每一步都换成 U
从 start 到 LCA 是往上走,只能记 U;只有从 LCA 到 dest 才是往下的 L/R
✗ 错:忘了去掉公共前缀
✓ 对:先跳过 a、b 的最长公共前缀再拼
公共前缀是两点共享的「根到 LCA」那段,既不该往上也不该重复往下,必须先砍掉
✗ 错:回溯时只改方向不撤销
✓ 对:左右都失败要把这一步从路径里 pop 掉
不弹出,错误的方向会留在路径串里污染结果
完整代码(Python / C++ / Java)
Python
from typing import List, Optional
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val=val; self.left=left; self.right=right
class Solution:
def getDirections(self, root: Optional[TreeNode], startValue: int, destValue: int) -> str:
def path(n,val,p):
if not n: return False
if n.val==val: return True
p.append('L')
if path(n.left,val,p): return True
p[-1]='R'
if path(n.right,val,p): return True
p.pop(); return False
a=[]; b=[]; path(root,startValue,a); path(root,destValue,b)
i=0
while i<len(a) and i<len(b) and a[i]==b[i]: i+=1
return 'U'*(len(a)-i)+''.join(b[i:])C++
#include <algorithm>
#include <functional>
#include <queue>
#include <string>
#include <unordered_map>
#include <vector>
using namespace std;
struct TreeNode{int val;TreeNode* left;TreeNode* right;TreeNode():val(0),left(nullptr),right(nullptr){} TreeNode(int x):val(x),left(nullptr),right(nullptr){} TreeNode(int x,TreeNode*l,TreeNode*r):val(x),left(l),right(r){}};
class Solution{public: string getDirections(TreeNode* root,int startValue,int destValue){ string a,b; path(root,startValue,a); path(root,destValue,b); int i=0; while(i<a.size()&&i<b.size()&&a[i]==b[i])i++; return string(a.size()-i,'U')+b.substr(i);} bool path(TreeNode*n,int val,string& p){ if(!n)return false; if(n->val==val)return true; p.push_back('L'); if(path(n->left,val,p))return true; p.back()='R'; if(path(n->right,val,p))return true; p.pop_back(); return false;} };Java
import java.util.*;
class TreeNode{int val;TreeNode left,right;TreeNode(){} TreeNode(int val){this.val=val;} TreeNode(int val,TreeNode left,TreeNode right){this.val=val;this.left=left;this.right=right;}}
class Solution{ public String getDirections(TreeNode root,int startValue,int destValue){ StringBuilder a=new StringBuilder(),b=new StringBuilder(); path(root,startValue,a); path(root,destValue,b); int i=0; while(i<a.length()&&i<b.length()&&a.charAt(i)==b.charAt(i))i++; StringBuilder ans=new StringBuilder(); for(int j=i;j<a.length();j++)ans.append('U'); ans.append(b.substring(i)); return ans.toString();} boolean path(TreeNode n,int val,StringBuilder p){ if(n==null)return false; if(n.val==val)return true; p.append('L'); if(path(n.left,val,p))return true; p.setCharAt(p.length()-1,'R'); if(path(n.right,val,p))return true; p.deleteCharAt(p.length()-1); return false;} }复杂度
时间
O(n)
两次 DFS 各最多访问全部 n 个节点求路径,去公共前缀和拼串都不超过路径长度,合起来线性
空间
O(h)
递归栈深度等于树高 h,两条路径串长度也不超过 h;最坏树退化成链时 h 为 n
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 从一个节点到另一个节点每一步的方向 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
能不能不分别求两条完整路径,直接找 LCA 再算?+
可以。先用常规方法求出 start 和 dest 的 LCA,再分别求 LCA 到 start 的深度差(记这么多 U)和 LCA 到 dest 的方向串(L/R)。不过「求两条根路径再砍公共前缀」写起来更直接,公共前缀的终点天然就是 LCA,一举两得。
路径串可能很长吗,会不会爆?+
路径长度就是节点到根的深度,最多 O(h)、即树高;最坏树退化成链时是 O(n)。两条串加起来仍是 O(n) 级,不会爆,字符也只有 U/L/R 三种。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 从一个节点到另一个节点每一步的方向 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。