二叉树的所有路径:把已经走过的字往下传
每条根到叶的路是一串新字符串。Go 里字符串按值传递,左支拼完的那份带不走右支的现场。
DFS:递归参数携带「已走过的路径字符串」。到叶子节点时,把当前字符串(或值拼接)加入结果。每条分支路径独立,不必回溯——因为字符串按值传递,天然隔离。时间 O(n²),空间 O(h)。
给定一棵二叉树的根,返回所有从根走到叶子的路径,格式写成 "1->2->5" 这种箭头串。叶子是左右都空的节点;只走到半路、还没碰到叶子,不能交。
主例树是 [1, 2, 3, null, 5]:根 1,左 2 再往右到 5,右孩子是 3。两条根到叶:1→2→5 和 1→3,答案 ["1->2->5", "1->3"]。LC113 还要路径和等于目标;这里不求和,只收集字符串。
把每条路都从根再走一遍能做对,但同一段前缀 1→2 会被重复拼接。本文要回答:为什么把「已经拼好的前缀」当参数往下传就够了,以及主例从左叶回到根再走右叶时,现场为什么不会脏。
缺口是前缀串,不是一棵要改的共享数组
第一直觉是先遍历,碰到叶子再从根把节点值抠出来拼。结果能对,但每条路都要回头找父亲,或者在外面另存一张「节点到父」的表。更糟的做法是共用一个切片往前 push:走到 5 时切片是 [1,2,5],若不 pop 就去走 3,3 会接到 5 后面,拼出幽灵路径。
缺的不是「有哪些叶子」,而是进入某个节点时,从根到它的父亲已经写好的那截字。当前节点只负责把自己的值接上去;若自己是叶子,这串字就是一条答案;若不是,把新串分别交给左右孩子。问题从「整棵树有哪些路」收成「带着前缀,继续往下走」。
递归参数 s 是「从根走到当前节点之前」的字符串。进入节点 n:若 s 为空,说明这是根,s' 就是 n 的值;否则 s' = s + "->" + n 的值。这一个拼接动作同时处理了根和中间节点,箭头不会出现在开头,也不会在叶子后再多画一个。
Go 的 string 赋值和 s += 都会得到一份新串,调用者手里的 s 不变。左孩子递归里拼出的 "1->2->5" 是新值,回到节点 1 时,参数 s 仍是 "1"。右孩子拿到的仍是 "1",再拼成 "1->3"。所以这套写法没有 push/pop——不是忘了回溯,是值传递把隔离做完了。
若改成共享切片 path, isolation 就没了:进入时 append,离开时必须 path = path[:len-1],否则兄弟分支会看见别人留下的尾。LC113 走的就是切片这条路。两种写法收集的是同一批路径,恢复现场的责任不同。
用手走主例。从根进入,s 空,拼出 "1",1 不是叶子。先走左孩子 2:传入 "1",拼成 "1->2"。2 还有右孩子 5:传入 "1->2",拼成 "1->2->5",5 左右皆空,收下第一条。演示场景前三帧就是这一条左路,path 从 [1] 长到 [1,2,5]。
从 5 返回 2,再返回 1。1 的参数仍是 "1"。再走右孩子 3:拼成 "1->3",3 是叶子,收下第二条。演示最后一帧两条都在。空树没有路径;单节点树只有一条、等于根的值,不会多出箭头。时间主要花在拼接和拷贝字符串上,最坏每条路径长度 O(n),所以是 O(n²),不是 O(n)。
按值传递字符串每次拼接都是新值,左支改不了右支的参数。不必为这题强行写 push/pop。若路径改成切片,离开节点时就要 pop,那是 LC113 的合同,不是这题字符串写法的合同。
Go:DFS 传字符串
func binaryTreePaths(root *TreeNode) []string {res := []string{}var dfs func(*TreeNode, string)dfs = func(n *TreeNode, s string) {if n == nil { return }if s == "" { s = strconv.Itoa(n.Val) } else { s += "->" + strconv.Itoa(n.Val) }if n.Left == nil && n.Right == nil {res = append(res, s)return}dfs(n.Left, s)dfs(n.Right, s)}dfs(root, "")return res}
1s 是调用者给的前缀。函数里再赋值或 +=,改的是本帧的 s,外面那份还在。
2根的 s 为空,只写入节点值;其后每次先加 "->" 再加值,避免 "->1->2"。
3左右都空才是叶子,此时整串入结果。有一个孩子就还要往下传,不能提前收。
4左右各传当前这份 s。主例回到 1 再走 3 时,传入的仍是 "1",不是 "1->2->5"。
总结
前缀字符串当参数往下传,叶子处收下。主例两条:1->2->5 与 1->3。
- 字符串按值传递,分支天然隔离,这套写法不必 push/pop。
- 叶子是唯一收集点。根单独成树时答案就是根的值。
- 若路径改成切片,离开就要 pop,那是 LC113,不是本题字符串合同。