LeetCode 988中等树/DFS
从叶结点开始的最小字符串 图解题解
这道题到底在问什么
给定二叉树 root,节点值 0..25 对应 a..z。对每个叶子,取「叶子→根」路径上的字母组成字符串,返回字典序最小的一个。
- 输入
- root=[0,1,2,3,4,3,4]
- 输出
- "dba"
最优解:一步一步想明白
- 3记住「下行追加、到叶成串比最小、回溯弹出」,下面每帧都在套它。
- 4从根开始 DFS开局路径为空、最小串还没有。DFS 从根出发,沿左右孩子一路下探到每个叶子。
- 5path = a走到节点 a(紫色),把它追加进当前路径,现在根到这里是 a。它还有孩子,继续往下走。
- 6path = ab走到节点 b(紫色),把它追加进当前路径,现在根到这里是 ab。它还有孩子,继续往下走。
- 7path = abd走到节点 d(紫色),把它追加进当前路径,现在根到这里是 abd。它没有孩子,是个叶子。
- 8候选 dba ← 更小到达叶子 d。把路径反过来(叶→根)拼成候选串 “dba”。它比之前的最小串更小,刷新最小为 “dba”。
- 9弹出 d节点 d 的子树都试完了,回溯:把它从路径里弹出(标蓝表示已处理),回到它的父节点继续。
- 10path = abe走到节点 e(紫色),把它追加进当前路径,现在根到这里是 abe。它没有孩子,是个叶子。
- 11候选 eba到达叶子 e。把路径反过来(叶→根)拼成候选串 “eba”。它不比当前最小 “dba” 小,保持不变。
- 12弹出 e节点 e 的子树都试完了,回溯:把它从路径里弹出(标蓝表示已处理),回到它的父节点继续。
- 13弹出 b节点 b 的子树都试完了,回溯:把它从路径里弹出(标蓝表示已处理),回到它的父节点继续。
- 14path = ac走到节点 c(紫色),把它追加进当前路径,现在根到这里是 ac。它还有孩子,继续往下走。
- 15path = acd走到节点 d(紫色),把它追加进当前路径,现在根到这里是 acd。它没有孩子,是个叶子。
- 16候选 dca到达叶子 d。把路径反过来(叶→根)拼成候选串 “dca”。它不比当前最小 “dba” 小,保持不变。
- 17弹出 d节点 d 的子树都试完了,回溯:把它从路径里弹出(标蓝表示已处理),回到它的父节点继续。
- 18path = ace走到节点 e(紫色),把它追加进当前路径,现在根到这里是 ace。它没有孩子,是个叶子。
- 19候选 eca到达叶子 e。把路径反过来(叶→根)拼成候选串 “eca”。它不比当前最小 “dba” 小,保持不变。
- 20弹出 e节点 e 的子树都试完了,回溯:把它从路径里弹出(标蓝表示已处理),回到它的父节点继续。
- 21弹出 c节点 c 的子树都试完了,回溯:把它从路径里弹出(标蓝表示已处理),回到它的父节点继续。
- 22弹出 a,结束根节点 a 的左右子树都试完了,把它从路径弹出,DFS 到此全部结束——所有根到叶的路径都比过了。
- 23答案 = dba四个叶子分别给出 dba、eba、dca、eca,字典序最小的是 “dba”,就是答案。整个过程一次 DFS、回溯时弹出,路径始终保持「根到当前」。
⚠️ 容易写错的地方
✗ 错:直接用「根→叶」方向比较
✓ 对:题目要「叶→根」,要把路径反转
方向反了字典序结果完全不同
✗ 错:回溯时忘了 path.pop()
✓ 对:离开节点必须弹出
不弹出会污染兄弟分支的路径
✗ 错:用根到叶的前缀比较来剪枝
✓ 对:本题不能简单按前缀剪枝
因为比较的是反转串,短前缀小不代表整串小
完整代码(Python / C++ / Java)
Python
from typing import Optional
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def smallestFromLeaf(self, root: Optional[TreeNode]) -> str:
best = None
def dfs(node, path):
nonlocal best
if not node:
return
path.append(chr(ord('a') + node.val))
if not node.left and not node.right:
cand = ''.join(reversed(path))
if best is None or cand < best:
best = cand
else:
dfs(node.left, path)
dfs(node.right, path)
path.pop()
dfs(root, [])
return best or ''C++
#include <string>
#include <algorithm>
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 smallestFromLeaf(TreeNode* root) {
string path, best;
dfs(root, path, best);
return best;
}
private:
void dfs(TreeNode* node, string& path, string& best) {
if (!node) return;
path.push_back(char('a' + node->val));
if (!node->left && !node->right) {
string cand(path.rbegin(), path.rend());
if (best.empty() || cand < best) best = cand;
} else {
dfs(node->left, path, best);
dfs(node->right, path, best);
}
path.pop_back();
}
};Java
import java.util.*;
class TreeNode { int val; TreeNode left; TreeNode 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 {
private String best = null;
public String smallestFromLeaf(TreeNode root) {
best = null;
dfs(root, new StringBuilder());
return best == null ? "" : best;
}
private void dfs(TreeNode node, StringBuilder path) {
if (node == null) return;
path.append((char)('a' + node.val));
if (node.left == null && node.right == null) {
String cand = path.reverse().toString();
path.reverse();
if (best == null || cand.compareTo(best) < 0) best = cand;
} else {
dfs(node.left, path);
dfs(node.right, path);
}
path.deleteCharAt(path.length() - 1);
}
}复杂度
时间
O(n+ΣL),最坏 O(nh)
遍历 O(n);每个叶子反转/拼/比较串长 O(h);叶子又多又深(如梳子形树)时累加可达 O(n²)
空间
O(h)
递归栈深 + 当前路径,均为树高 h(最坏 O(n))
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 从叶结点开始的最小字符串 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
能不能像比较「根→叶」那样边走边剪枝?+
不能简单剪。因为比较的是「叶→根」反转串,路径越深,新加的字母排在串的越前面(更高位),会改变整个字典序。所以必须走到叶子、拼出完整候选再比较,不能靠根侧前缀提前判断。
如何降低拼接/比较成本?+
可以维护一个可变字符数组,下行 push、回溯 pop;到叶子时再反转比较。也可以用「按字符逐位比较 best」的方式避免每次都构造完整字符串,但实现更复杂,面试讲清思路即可。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 从叶结点开始的最小字符串 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。