DFS递归中必须手动回溯路径,因为引用传递避免拷贝但需pop_back()清理,否则路径污染;空节点不构成有效路径,仅叶子节点(左右均空)才存入结果。

为什么 dfs 递归中必须手动回溯路径?
因为 C++ 的 std::vector 和 std::string 是值语义,传入递归函数时若用值传递(如 vector<int> path</int>),每次递归调用都拷贝一份,看似“自动隔离”,但会显著拖慢性能——尤其树深大、节点多时。而用引用传递(vector<int>& path</int>)能避免拷贝,代价是必须在返回前手动弹出当前节点,否则上层看到的是污染后的路径。
常见错误现象:["1->2->3", "1->2->3->4"] 这类重复或过长路径,往往就是忘了在 dfs 返回前执行 path.pop_back()。
- 使用场景:所有需要记录完整路径的 DFS 遍历,不只是二叉树,也适用于图的路径枚举
- 参数差异:传
vector<int>&</int>+ 手动push_back/pop_back,比传vector<int></int>值参快 3–5 倍(实测千级节点树) - 性能影响:不回溯 → 路径容器持续增长 → 内存占用线性上升 + 后续 push 可能触发多次 realloc
TreeNode* 为空时到底该不该加入结果?
不该。叶子节点的判定依据是 !root->left && !root->right,而非 !root。空指针 nullptr 本身不是路径终点,只是递归边界。一旦在 if (!root) 分支里把空路径加入 res,就会得到一堆空字符串或非法路径。
典型错误写法:
if (!root) {
res.push_back(path_str);
return;
} —— 这会让每个 null 子节点都塞一条空路径进去。
- 正确做法:只在确认是叶子节点时构建并保存路径,即进入
if (!root->left && !root->right)分支才res.push_back(...) - 兼容性注意:LeetCode 测试用例包含单节点树(
root不空,左右都空),也包含空树(root == nullptr),后者应直接返回空vector
路径拼接用 to_string 还是 stringstream?
对单个整数,std::to_string 更轻量;但拼接含分隔符的路径(如 "1->2->3")时,反复调用 += 字符串效率不如一次性构造。更关键的是:避免在循环中频繁字符串拼接引发多次内存分配。
立即学习“C++免费学习笔记(深入)”;
推荐组合:path_str += (path_str.empty() ? "" : "->") + to_string(root->val); —— 利用短路和空检查,不依赖额外变量。
- 容易踩的坑:用
ostringstream每次新建对象,反而增加构造/析构开销;或用path_str = path_str + "->" + to_string(...),触发隐式拷贝 - 如果路径需输出为整数向量(非字符串),直接存
vector<int></int>,最后统一转——避免中间字符串化
如何验证你的路径结果没漏、没重、格式对?
最直接的方法:在递归入口打日志(调试版),或写个小验证函数比对节点值序列。例如对输入 [1,2,3,null,5],预期路径是 ["1->2->5","1->3"],而非 ["1->2","1->2->5","1->3"](漏了 2 的右子为空,但误把 2 当叶子)。
- 检查点一:每条结果路径长度 ≥ 1(空树除外),且首元素等于
root->val - 检查点二:路径数量应等于叶子节点数量(可用 BFS 先统计一次验证)
- 检查点三:任意两条路径的公共前缀,必须对应树中某条从根出发的真实分支
真正难处理的不是逻辑,是边界交织:空树、单节点、只有左子树、有空子节点的非叶子节点……这些情况混在一起时,if 分支顺序和判断条件稍有错位,结果就偏了。


















