题目描述
思路解析
一句话答案:LeetCode 606 根据二叉树创建字符串:按前序遍历边走边拼串,叶子只写值、右孩子空就省掉那组括号、左空右非空必须留一对 () 占位保住一一映射,时间 O(n)。
要把二叉树变成什么样的字符串
给一棵二叉树的根 root,按前序遍历,也就是先写自己再写左右子树,压成一行字符串:节点值在前,左右子树各用一对括号包住。省掉某对空括号后字符串还能唯一还原回这棵树,就省掉它。题面 root=[1,2,3,4] 拼成 "1(2(4))(3)",另一个只挪一个孩子,差别全在左孩子在不在。
括号全写满,串里会多出哪些空壳
一个偷懒的拼法是每个节点都写成 值(左子树)(右子树)。这样叶子 4 会拼成 "4()()"、只有左孩子的节点右边也拖一对 (),满树都是空气括号。可题目要省掉所有不影响还原的空括号,写满的留不住。
右孩子空能省、左孩子空却不能,差在哪
省括号的底线是省完后字符串还能唯一读回原来那棵树。右孩子为空时那对括号能省:左孩子已写在前面、位置定死,后面不再冒括号就代表右边没东西,只有左孩子的节点写成 值(左) 就够。但左孩子空、右孩子还在时那对空 () 必须留:省成 值(右),读串时会把里头第一段当成左孩子,右孩子被顶到左孩子的位置、成了另一棵树;留一对空 () 占位,后面那对 (右) 才认成右孩子。
递归到一个节点,该返回哪一段串
把拼串写成递归函数,每到一个节点只返回它这一小段。空节点返回空串;叶子,也就是左右孩子都没有,直接返回值、不添括号;右孩子空、左孩子在的节点返回 值(左),右边不写;右孩子在时不管左孩子,都返回 值(左)(右)——左孩子若空,左段是空串,拼出来正好是 值()(右),那对必须留的空 () 就这么冒出来。判完这四种,根返回的那段就是答案。
两棵四节点的树,分别拼成什么
先看 root=[1,2,3,4]:1 左孩子 2、右孩子 3,2 左孩子 4。1 左右都在,拼 值(左)(右)。到 2,右孩子空、左孩子 4 在,返回 值(左),4 是叶子返回 "4",2 这段是 "2(4)";3 是叶子返回 "3"。1 拼成 "1(2(4))(3)",跟题面输出一致。
再看 root=[1,2,3,null,4],2 的左孩子挪空、右孩子换成 4。到 2 时左孩子空、右孩子 4 在,落进最后一种,返回 值(左)(右):左边空串、右边 4 返回 "4",拼成 "2()(4)",中间那对空 () 正占住空左孩子的位置。1 和 3 不变,最终 "1(2()(4))(3)",差别只在 2 这段那对空括号留没留。
拼串会不会拖慢,哪三种树最该拿去试
遍历每个节点一次是 O(n)。但参考解法递归返回一整段字符串再往上拼,像 Python 的 f-string、C++/Java 的字符串相加,每拼一次都要复制下层整段;链状的树上越靠上复制的串越长,累计最坏滑到 O(n²)。想稳在 O(n),改用 StringBuilder 往末尾追加、不反复复制即可。空间是递归栈深度 O(h),链状树最坏 O(n)。
拿三种树戳:单节点的树只该吐一个值、别拖括号;左孩子空、右孩子在的树,那对空 () 得原样还在,省了右子树就会被当成左孩子;只有左孩子的树,右边整组括号得省掉,多一对就不是最简。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
记住三句话:值在前、左子树必带括号、右子树有才带。空括号只在「左空右非空」时保留,下面每帧都在套它。
准备 · 前序遍历:开局答案串为空。我们从根节点 50 开始,按前序遍历:先写当前节点,再写左子树、右子树。盯住右侧的「已生成串」,它会一个字符一个字符地长出来。
进入 · 写 50:走到节点 50(紫色),前序遍历第一步就是把它的值写进串,现在串是 50。
判定 50 的孩子:50 左右孩子都在,接下来要写「(左子树)(右子树)」两组括号。
50 · 左括号:给 50 写下左括号,准备把它的左子树装进去。串变成 50(。
进入 · 写 30:走到节点 30(紫色),前序遍历第一步就是把它的值写进串,现在串是 50(30。
判定 30 的孩子:30 左孩子空、但右孩子 60 在,这就是关键边界:左边要保留一对空括号 "()" 占位。
30 · 左括号:给 30 写下左括号,准备把它的左子树装进去。串变成 50(30(。
30 · 空左括号 ():30 左孩子是空的,但因为右孩子还在,这对括号不能省。写成空的 "()" 占住左孩子的位置,否则右子树会被误读成左孩子。串变成 50(30()。
30 · 右子树开括号:给 30 写下右子树的开括号 (,进入它的右子树。串变成 50(30()(。
进入 · 写 60:走到节点 60(紫色),前序遍历第一步就是把它的值写进串,现在串是 50(30()(60。
叶子 60:60 左右都没有孩子,是叶子。叶子后面什么括号都不写,直接它的值就完事,标绿表示已处理。
30 · 闭合右括号:30 的右子树写完,补上闭合的右括号。现在串是 50(30()(60)。
收尾 30:30 的左右子树全部写完,它对应的片段就是 30()(60),标绿收工。
50 · 闭合左括号:50 的左子树写完了,补上闭合的右括号,左半部分收口。现在串是 50(30()(60))。
50 · 右子树开括号:给 50 写下右子树的开括号 (,进入它的右子树。串变成 50(30()(60))(。
进入 · 写 80:走到节点 80(紫色),前序遍历第一步就是把它的值写进串,现在串是 50(30()(60))(80。
判定 80 的孩子:80 只有左孩子、右孩子是空的,左括号要写,右边那组括号可以省略。
80 · 左括号:给 80 写下左括号,准备把它的左子树装进去。串变成 50(30()(60))(80(。
进入 · 写 70:走到节点 70(紫色),前序遍历第一步就是把它的值写进串,现在串是 50(30()(60))(80(70。
叶子 70:70 左右都没有孩子,是叶子。叶子后面什么括号都不写,直接它的值就完事,标绿表示已处理。
80 · 闭合左括号:80 的左子树写完了,补上闭合的右括号,左半部分收口。现在串是 50(30()(60))(80(70)。
80 · 右子树括号整组省略:80 右孩子是空的,而且省掉不会破坏一一映射,所以右边那对括号整组省略,一个字符都不写。
收尾 80:80 的左右子树全部写完,它对应的片段就是 80(70),标绿收工。
50 · 闭合右括号:50 的右子树写完,补上闭合的右括号。现在串是 50(30()(60))(80(70))。
收尾 50:50 的左右子树全部写完,它对应的片段就是 50(30()(60))(80(70)),标绿收工。
完成 · 答案:整棵树前序走完,最终答案是 50(30()(60))(80(70))。回看两处省略:30 的左孩子空但右孩子在,保留了 "()";80 只有左孩子,右子树那组括号被整组省掉。这正是题目省略规则的精髓。
三个边界把规则两端都覆盖:单节点、左空右非空(留括号)、只有左孩子(省右子树括号)。
面试就盯这一处不对称:左空要留、右空要省,本质都是为了保住一一映射。
参考代码
from __future__ import annotationsfrom typing import *from collections import *from functools import *from itertools import *from math import *from heapq import *from bisect import *class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = rightclass ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next# Definition for a binary tree node.# class TreeNode:# def __init__(self, val=0, left=None, right=None):# self.val = val# self.left = left# self.right = rightclass Solution: def tree2str(self, root: Optional[TreeNode]) -> str: def dfs(root): if root is None: return '' if root.left is None and root.right is None: return str(root.val) if root.right is None: return f'{root.val}({dfs(root.left)})' return f'{root.val}({dfs(root.left)})({dfs(root.right)})' return dfs(root)复杂度
- 时间:O(n)~O(n²),遍历每个节点是 O(n);但本题参考解是「递归返回整段字符串直接拼接」(Python f-string / C++ string + / Java +),链状树上最坏会退化到 O(n²);要稳定 O(n) 需用 StringBuilder / list buffer 等可变缓冲追加
- 空间:O(h),递归栈深度等于树高 h,最坏(链状树)O(n);不计输出串
易错点
面试追问把动画讲成自己的话
追问为什么「左空右非空」那对空括号不能省?
追问为什么「右孩子空」反而能省右子树括号?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
在二叉树中增加一行
LeetCode 623 · 中等 · 沿着 树套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题