二叉树边界遍历(Boundary of Binary Tree)题解:三步拆分法与单次前序遍历法
2026/9/17 16:58:36 网站建设 项目流程

二叉树边界遍历(Boundary of Binary Tree)题解:三步拆分法与单次前序遍历法

【免费下载链接】leetcodeLeetcode solutions项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode

导读

二叉树的边界(Boundary)是指沿着树的外轮廓,从根节点出发逆时针绕树一圈所经过的全部节点,它由"左边界(自上而下,不含叶子)、全部叶子节点(自左而右)、右边界(自下而上,不含叶子)"三部分组成。本篇文章基于仓库文档 articles/boundary-of-binary-tree.md 展开,系统讲解两种主流解法:分三部分独立收集的简单方案单次前序遍历 + 标志位分类方案。读完本文,你将掌握边界节点的判定规则、右边界逆序输出的处理技巧,以及如何用一次 DFS 完成全部收集,并能规避叶子节点重复、右边界顺序颠倒、单子节点漏收集三类高频陷阱。


前置知识

在动手实现之前,需要先具备以下基础:

  • 二叉树遍历(Binary Tree Traversal)——理解前序(preorder)、中序(inorder)、后序(postorder)三种遍历模式的访问顺序。仓库中 articles/binary-tree-preorder-traversal.md、articles/binary-tree-inorder-traversal.md、articles/binary-tree-postorder-traversal.md 分别对这三种遍历做了专门讲解,可作为对照参考。
  • 递归(Recursion)——使用递归函数遍历树结构并收集节点,这是收集叶子节点与执行前序遍历的基础。
  • 栈(Stack Data Structure)——利用栈的后进先出特性反转元素顺序,右边界(bottom-to-top)的逆序输出正是借助栈完成的。

问题定义:什么是二叉树的边界

给定一棵二叉树的根节点root,按逆时针方向返回树的边界节点值列表。边界由三部分拼接而成:

  1. 左边界(Left Boundary):从根节点的左孩子出发,沿left优先、缺失时沿right下行,排除叶子节点,顺序为自上而下;
  2. 叶子节点(Leaves):整棵树的所有叶子,按从左到右的顺序收集;
  3. 右边界(Right Boundary):从根节点的右孩子出发,沿right优先、缺失时沿left下行,排除叶子节点,输出顺序为自下而上。

根节点单独处理:若根不是叶子,则作为结果的第一个元素输出。


解法一:简单方案(分三部分收集)

直觉

既然边界天然可以拆成"左边界 + 叶子 + 右边界"三段,最直观的做法就是分别处理每一段:沿着左边缘一路下行收集左边界;通过递归收集所有叶子;沿着右边缘下行时把节点压入栈中,最后统一弹出实现逆序,从而得到自下而上的右边界。

算法步骤

  1. 若根节点为null,直接返回空列表;
  2. 若根节点不是叶子,先把根的值加入结果;
  3. 遍历左边界:从root.left出发,始终优先走向左孩子(左孩子为空时走向右孩子),只收集非叶子节点;
  4. 用递归辅助函数收集所有叶子节点,保证从左到右的顺序;
  5. 遍历右边界:从root.right出发,始终优先走向右孩子(右孩子为空时走向左孩子),把非叶子节点压入栈;
  6. 依次弹出栈中元素加入结果(完成逆序),返回结果。

多语言实现

:::tabs-start

class Solution: def isLeaf(self, t: Optional[TreeNode]) -> bool: return t.left is None and t.right is None def addLeaves(self, res: List[int], root: Optional[TreeNode]) -> None: if self.isLeaf(root): res.append(root.val) else: if root.left is not None: self.addLeaves(res, root.left) if root.right is not None: self.addLeaves(res, root.right) def boundaryOfBinaryTree(self, root: Optional[TreeNode]) -> List[int]: res = [] if root is None: return res if not self.isLeaf(root): res.append(root.val) t = root.left while t is not None: if not self.isLeaf(t): res.append(t.val) if t.left is not None: t = t.left else: t = t.right self.addLeaves(res, root) stack = [] t = root.right while t is not None: if not self.isLeaf(t): stack.append(t.val) if t.right is not None: t = t.right else: t = t.left while stack: res.append(stack.pop()) return res
class Solution { public boolean isLeaf(TreeNode t) { return t.left == null && t.right == null; } public void addLeaves(List<Integer> res, TreeNode root) { if (isLeaf(root)) { res.add(root.val); } else { if (root.left != null) { addLeaves(res, root.left); } if (root.right != null) { addLeaves(res, root.right); } } } public List<Integer> boundaryOfBinaryTree(TreeNode root) { ArrayList<Integer> res = new ArrayList<>(); if (root == null) { return res; } if (!isLeaf(root)) { res.add(root.val); } TreeNode t = root.left; while (t != null) { if (!isLeaf(t)) { res.add(t.val); } if (t.left != null) { t = t.left; } else { t = t.right; } } addLeaves(res, root); Stack<Integer> s = new Stack<>(); t = root.right; while (t != null) { if (!isLeaf(t)) { s.push(t.val); } if (t.right != null) { t = t.right; } else { t = t.left; } } while (!s.empty()) { res.add(s.pop()); } return res; } }
class Solution { public: bool isLeaf(TreeNode* t) { return t->left == nullptr && t->right == nullptr; } void addLeaves(vector<int>& res, TreeNode* root) { if (isLeaf(root)) { res.push_back(root->val); } else { if (root->left != nullptr) { addLeaves(res, root->left); } if (root->right != nullptr) { addLeaves(res, root->right); } } } vector<int> boundaryOfBinaryTree(TreeNode* root) { vector<int> res; if (root == nullptr) { return res; } if (!isLeaf(root)) { res.push_back(root->val); } TreeNode* t = root->left; while (t != nullptr) { if (!isLeaf(t)) { res.push_back(t->val); } if (t->left != nullptr) { t = t->left; } else { t = t->right; } } addLeaves(res, root); stack<int> s; t = root->right; while (t != nullptr) { if (!isLeaf(t)) { s.push(t->val); } if (t->right != nullptr) { t = t->right; } else { t = t->left; } } while (!s.empty()) { res.push_back(s.top()); s.pop(); } return res; } };
class Solution { /** * @param {TreeNode} root * @return {number[]} */ isLeaf(t) { return t.left === null && t.right === null; } addLeaves(res, root) { if (this.isLeaf(root)) { res.push(root.val); } else { if (root.left !== null) { this.addLeaves(res, root.left); } if (root.right !== null) { this.addLeaves(res, root.right); } } } boundaryOfBinaryTree(root) { const res = []; if (root === null) { return res; } if (!this.isLeaf(root)) { res.push(root.val); } let t = root.left; while (t !== null) { if (!this.isLeaf(t)) { res.push(t.val); } if (t.left !== null) { t = t.left; } else { t = t.right; } } this.addLeaves(res, root); const stack = []; t = root.right; while (t !== null) { if (!this.isLeaf(t)) { stack.push(t.val); } if (t.right !== null) { t = t.right; } else { t = t.left; } } while (stack.length > 0) { res.push(stack.pop()); } return res; } }
func isLeaf(t *TreeNode) bool { return t.Left == nil && t.Right == nil } func addLeaves(res *[]int, root *TreeNode) { if isLeaf(root) { *res = append(*res, root.Val) } else { if root.Left != nil { addLeaves(res, root.Left) } if root.Right != nil { addLeaves(res, root.Right) } } } func boundaryOfBinaryTree(root *TreeNode) []int { res := []int{} if root == nil { return res } if !isLeaf(root) { res = append(res, root.Val) } t := root.Left for t != nil { if !isLeaf(t) { res = append(res, t.Val) } if t.Left != nil { t = t.Left } else { t = t.Right } } addLeaves(&res, root) stack := []int{} t = root.Right for t != nil { if !isLeaf(t) { stack = append(stack, t.Val) } if t.Right != nil { t = t.Right } else { t = t.Left } } for len(stack) > 0 { res = append(res, stack[len(stack)-1]) stack = stack[:len(stack)-1] } return res }
class Solution { private fun isLeaf(t: TreeNode): Boolean { return t.left == null && t.right == null } private fun addLeaves(res: MutableList<Int>, root: TreeNode) { if (isLeaf(root)) { res.add(root.`val`) } else { root.left?.let { addLeaves(res, it) } root.right?.let { addLeaves(res, it) } } } fun boundaryOfBinaryTree(root: TreeNode?): List<Int> { val res = mutableListOf<Int>() if (root == null) return res if (!isLeaf(root)) { res.add(root.`val`) } var t = root.left while (t != null) { if (!isLeaf(t)) { res.add(t.`val`) } t = if (t.left != null) t.left else t.right } addLeaves(res, root) val stack = mutableListOf<Int>() t = root.right while (t != null) { if (!isLeaf(t)) { stack.add(t.`val`) } t = if (t.right != null) t.right else t.left } while (stack.isNotEmpty()) { res.add(stack.removeAt(stack.size - 1)) } return res } }
class Solution { func isLeaf(_ t: TreeNode) -> Bool { return t.left == nil && t.right == nil } func addLeaves(_ res: inout [Int], _ root: TreeNode) { if isLeaf(root) { res.append(root.val) } else { if let left = root.left { addLeaves(&res, left) } if let right = root.right { addLeaves(&res, right) } } } func boundaryOfBinaryTree(_ root: TreeNode?) -> [Int] { var res = [Int]() guard let root = root else { return res } if !isLeaf(root) { res.append(root.val) } var t = root.left while t != nil { if !isLeaf(t!) { res.append(t!.val) } t = t!.left != nil ? t!.left : t!.right } addLeaves(&res, root) var stack = [Int]() t = root.right while t != nil { if !isLeaf(t!) { stack.append(t!.val) } t = t!.right != nil ? t!.right : t!.left } while !stack.isEmpty { res.append(stack.removeLast()) } return res } }
impl Solution { pub fn boundary_of_binary_tree(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> { let mut res = Vec::new(); let root = match root { Some(r) => r, None => return res, }; if !Self::is_leaf(&root) { res.push(root.borrow().val); } // Left boundary let mut t = root.borrow().left.clone(); while let Some(node) = t { if !Self::is_leaf(&node) { res.push(node.borrow().val); } let next = if node.borrow().left.is_some() { node.borrow().left.clone() } else { node.borrow().right.clone() }; t = next; } // Leaves Self::add_leaves(&root, &mut res); // Right boundary (reversed) let mut stack = Vec::new(); t = root.borrow().right.clone(); while let Some(node) = t { if !Self::is_leaf(&node) { stack.push(node.borrow().val); } let next = if node.borrow().right.is_some() { node.borrow().right.clone() } else { node.borrow().left.clone() }; t = next; } while let Some(val) = stack.pop() { res.push(val); } res } fn is_leaf(node: &Rc<RefCell<TreeNode>>) -> bool { let n = node.borrow(); n.left.is_none() && n.right.is_none() } fn add_leaves(node: &Rc<RefCell<TreeNode>>, res: &mut Vec<i32>) { if Self::is_leaf(node) { res.push(node.borrow().val); } else { if let Some(ref left) = node.borrow().left { Self::add_leaves(left, res); } if let Some(ref right) = node.borrow().right { Self::add_leaves(right, res); } } } }

::tabs-end

复杂度分析

  • 时间复杂度:$O(n)$
  • 空间复杂度:$O(n)$

其中 $n$ 为树中节点的个数。时间上,左边界、右边界、叶子三部分恰好遍历整棵树各节点一次;空间上,最坏情况(链状树)下递归深度与栈大小均为 $O(n)$。


解法二:单次前序遍历 + 标志位分类

直觉

简单方案需要三段独立的遍历,而边界收集其实可以在一次前序遍历中完成:遍历时给每个节点打上一个"角色标志",据此决定把它放进哪一类容器。标志共四种:根节点(0)、左边界(1)、右边界(2)、内部节点(3)。左边界节点直接按序追加到left_boundary,右边界节点逆序收集到right_boundary(通过头部插入实现),叶子单独收集到leaves,最后拼接left_boundary + leaves + right_boundary即可。

标志位传播规则

遍历到每个节点时,需要依据当前节点的标志子节点的存在情况,为其左右孩子计算新的标志:

孩子当前标志兄弟节点情况孩子标志含义
左孩子0(根)或1(左边界)任意1继承左边界身份
左孩子2(右边界)cur.right == null(无右兄弟)2右边界上的独子,仍属右边界
左孩子其他其他3内部节点
右孩子0(根)或2(右边界)任意2继承右边界身份
右孩子1(左边界)cur.left == null(无左兄弟)1左边界上的独子,仍属左边界
右孩子其他其他3内部节点

核心思想:左边界节点沿外轮廓下行时,若唯一的孩子在右侧,则这个"独子"依然贴着边界,必须继承边界身份;右边界同理。这保证了一棵退化(链状)树的边界依然能被完整收集。

算法步骤

  1. 创建三个列表:left_boundaryright_boundaryleaves
  2. 从根节点(标志0)开始执行前序遍历:
    • 若节点属于右边界(标志2):将其值头部插入right_boundary(等效于收集后逆序);
    • 若节点属于左边界或根(标志01):将其值追加到left_boundary
    • 若节点是叶子且未被计入边界:追加到leaves
  3. 对每个孩子,按上表规则计算其标志后递归;
  4. 最后拼接left_boundary + leaves + right_boundary并返回。

多语言实现

:::tabs-start

class Solution: def boundaryOfBinaryTree(self, root: Optional[TreeNode]) -> List[int]: left_boundary, right_boundary, leaves = [], [], [] self.preorder(root, left_boundary, right_boundary, leaves, 0) left_boundary.extend(leaves) left_boundary.extend(right_boundary) return left_boundary def is_leaf(self, cur): return cur.left is None and cur.right is None def is_right_boundary(self, flag): return flag == 2 def is_left_boundary(self, flag): return flag == 1 def is_root(self, flag): return flag == 0 def left_child_flag(self, cur, flag): if self.is_left_boundary(flag) or self.is_root(flag): return 1 elif self.is_right_boundary(flag) and cur.right is None: return 2 else: return 3 def right_child_flag(self, cur, flag): if self.is_right_boundary(flag) or self.is_root(flag): return 2 elif self.is_left_boundary(flag) and cur.left is None: return 1 else: return 3 def preorder(self, cur, left_boundary, right_boundary, leaves, flag): if cur is None: return if self.is_right_boundary(flag): right_boundary.insert(0, cur.val) elif self.is_left_boundary(flag) or self.is_root(flag): left_boundary.append(cur.val) elif self.is_leaf(cur): leaves.append(cur.val) self.preorder(cur.left, left_boundary, right_boundary, leaves, self.left_child_flag(cur, flag)) self.preorder(cur.right, left_boundary, right_boundary, leaves, self.right_child_flag(cur, flag))
class Solution { public List < Integer > boundaryOfBinaryTree(TreeNode root) { List < Integer > left_boundary = new LinkedList < > (), right_boundary = new LinkedList < > (), leaves = new LinkedList < > (); preorder(root, left_boundary, right_boundary, leaves, 0); left_boundary.addAll(leaves); left_boundary.addAll(right_boundary); return left_boundary; } public boolean isLeaf(TreeNode cur) { return (cur.left == null && cur.right == null); } public boolean isRightBoundary(int flag) { return (flag == 2); } public boolean isLeftBoundary(int flag) { return (flag == 1); } public boolean isRoot(int flag) { return (flag == 0); } public int leftChildFlag(TreeNode cur, int flag) { if (isLeftBoundary(flag) || isRoot(flag)) return 1; else if (isRightBoundary(flag) && cur.right == null) return 2; else return 3; } public int rightChildFlag(TreeNode cur, int flag) { if (isRightBoundary(flag) || isRoot(flag)) return 2; else if (isLeftBoundary(flag) && cur.left == null) return 1; else return 3; } public void preorder(TreeNode cur, List < Integer > left_boundary, List < Integer > right_boundary, List < Integer > leaves, int flag) { if (cur == null) return; if (isRightBoundary(flag)) right_boundary.add(0, cur.val); else if (isLeftBoundary(flag) || isRoot(flag)) left_boundary.add(cur.val); else if (isLeaf(cur)) leaves.add(cur.val); preorder(cur.left, left_boundary, right_boundary, leaves, leftChildFlag(cur, flag)); preorder(cur.right, left_boundary, right_boundary, leaves, rightChildFlag(cur, flag)); } }
class Solution { public: vector<int> boundaryOfBinaryTree(TreeNode* root) { vector<int> left_boundary, right_boundary, leaves; preorder(root, left_boundary, right_boundary, leaves, 0); left_boundary.insert(left_boundary.end(), leaves.begin(), leaves.end()); left_boundary.insert(left_boundary.end(), right_boundary.begin(), right_boundary.end()); return left_boundary; } private: bool isLeaf(TreeNode* cur) { return cur->left == nullptr && cur->right == nullptr; } bool isRightBoundary(int flag) { return flag == 2; } bool isLeftBoundary(int flag) { return flag == 1; } bool isRoot(int flag) { return flag == 0; } int leftChildFlag(TreeNode* cur, int flag) { if (isLeftBoundary(flag) || isRoot(flag)) { return 1; } else if (isRightBoundary(flag) && cur->right == nullptr) { return 2; } else { return 3; } } int rightChildFlag(TreeNode* cur, int flag) { if (isRightBoundary(flag) || isRoot(flag)) { return 2; } else if (isLeftBoundary(flag) && cur->left == nullptr) { return 1; } else { return 3; } } void preorder(TreeNode* cur, vector<int>& left_boundary, vector<int>& right_boundary, vector<int>& leaves, int flag) { if (cur == nullptr) { return; } if (isRightBoundary(flag)) { right_boundary.insert(right_boundary.begin(), cur->val); } else if (isLeftBoundary(flag) || isRoot(flag)) { left_boundary.push_back(cur->val); } else if (isLeaf(cur)) { leaves.push_back(cur->val); } preorder(cur->left, left_boundary, right_boundary, leaves, leftChildFlag(cur, flag)); preorder(cur->right, left_boundary, right_boundary, leaves, rightChildFlag(cur, flag)); } };
class Solution { /** * @param {TreeNode} root * @return {number[]} */ boundaryOfBinaryTree(root) { const left_boundary = [], right_boundary = [], leaves = []; this.preorder(root, left_boundary, right_boundary, leaves, 0); left_boundary.push(...leaves); left_boundary.push(...right_boundary); return left_boundary; } isLeaf(cur) { return cur.left === null && cur.right === null; } isRightBoundary(flag) { return flag === 2; } isLeftBoundary(flag) { return flag === 1; } isRoot(flag) { return flag === 0; } leftChildFlag(cur, flag) { if (this.isLeftBoundary(flag) || this.isRoot(flag)) { return 1; } else if (this.isRightBoundary(flag) && cur.right === null) { return 2; } else { return 3; } } rightChildFlag(cur, flag) { if (this.isRightBoundary(flag) || this.isRoot(flag)) { return 2; } else if (this.isLeftBoundary(flag) && cur.left === null) { return 1; } else { return 3; } } preorder(cur, left_boundary, right_boundary, leaves, flag) { if (cur === null) { return; } if (this.isRightBoundary(flag)) { right_boundary.unshift(cur.val); } else if (this.isLeftBoundary(flag) || this.isRoot(flag)) { left_boundary.push(cur.val); } else if (this.isLeaf(cur)) { leaves.push(cur.val); } this.preorder( cur.left, left_boundary, right_boundary, leaves, this.leftChildFlag(cur, flag), ); this.preorder( cur.right, left_boundary, right_boundary, leaves, this.rightChildFlag(cur, flag), ); } }
func boundaryOfBinaryTree(root *TreeNode) []int { leftBoundary := []int{} rightBoundary := []int{} leaves := []int{} var preorder func(cur *TreeNode, flag int) preorder = func(cur *TreeNode, flag int) { if cur == nil { return } isLeaf := cur.Left == nil && cur.Right == nil if flag == 2 { rightBoundary = append([]int{cur.Val}, rightBoundary...) } else if flag == 1 || flag == 0 { leftBoundary = append(leftBoundary, cur.Val) } else if isLeaf { leaves = append(leaves, cur.Val) } leftFlag := 3 if flag == 1 || flag == 0 { leftFlag = 1 } else if flag == 2 && cur.Right == nil { leftFlag = 2 } rightFlag := 3 if flag == 2 || flag == 0 { rightFlag = 2 } else if flag == 1 && cur.Left == nil { rightFlag = 1 } preorder(cur.Left, leftFlag) preorder(cur.Right, rightFlag) } preorder(root, 0) leftBoundary = append(leftBoundary, leaves...) leftBoundary = append(leftBoundary, rightBoundary...) return leftBoundary }
class Solution { fun boundaryOfBinaryTree(root: TreeNode?): List<Int> { val leftBoundary = mutableListOf<Int>() val rightBoundary = mutableListOf<Int>() val leaves = mutableListOf<Int>() fun isLeaf(cur: TreeNode) = cur.left == null && cur.right == null fun leftChildFlag(cur: TreeNode, flag: Int): Int { return when { flag == 1 || flag == 0 -> 1 flag == 2 && cur.right == null -> 2 else -> 3 } } fun rightChildFlag(cur: TreeNode, flag: Int): Int { return when { flag == 2 || flag == 0 -> 2 flag == 1 && cur.left == null -> 1 else -> 3 } } fun preorder(cur: TreeNode?, flag: Int) { if (cur == null) return when { flag == 2 -> rightBoundary.add(0, cur.`val`) flag == 1 || flag == 0 -> leftBoundary.add(cur.`val`) isLeaf(cur) -> leaves.add(cur.`val`) } preorder(cur.left, leftChildFlag(cur, flag)) preorder(cur.right, rightChildFlag(cur, flag)) } preorder(root, 0) leftBoundary.addAll(leaves) leftBoundary.addAll(rightBoundary) return leftBoundary } }
class Solution { func boundaryOfBinaryTree(_ root: TreeNode?) -> [Int] { var leftBoundary = [Int]() var rightBoundary = [Int]() var leaves = [Int]() func isLeaf(_ cur: TreeNode) -> Bool { return cur.left == nil && cur.right == nil } func leftChildFlag(_ cur: TreeNode, _ flag: Int) -> Int { if flag == 1 || flag == 0 { return 1 } else if flag == 2 && cur.right == nil { return 2 } return 3 } func rightChildFlag(_ cur: TreeNode, _ flag: Int) -> Int { if flag == 2 || flag == 0 { return 2 } else if flag == 1 && cur.left == nil { return 1 } return 3 } func preorder(_ cur: TreeNode?, _ flag: Int) { guard let cur = cur else { return } if flag == 2 { rightBoundary.insert(cur.val, at: 0) } else if flag == 1 || flag == 0 { leftBoundary.append(cur.val) } else if isLeaf(cur) { leaves.append(cur.val) } preorder(cur.left, leftChildFlag(cur, flag)) preorder(cur.right, rightChildFlag(cur, flag)) } preorder(root, 0) leftBoundary.append(contentsOf: leaves) leftBoundary.append(contentsOf: rightBoundary) return leftBoundary } }
impl Solution { pub fn boundary_of_binary_tree(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> { let mut left_boundary = Vec::new(); let mut right_boundary = Vec::new(); let mut leaves = Vec::new(); Self::preorder(&root, 0, &mut left_boundary, &mut right_boundary, &mut leaves); left_boundary.extend(leaves); left_boundary.extend(right_boundary); left_boundary } fn is_leaf(node: &Rc<RefCell<TreeNode>>) -> bool { let n = node.borrow(); n.left.is_none() && n.right.is_none() } fn left_child_flag(cur: &Rc<RefCell<TreeNode>>, flag: i32) -> i32 { if flag == 1 || flag == 0 { 1 } else if flag == 2 && cur.borrow().right.is_none() { 2 } else { 3 } } fn right_child_flag(cur: &Rc<RefCell<TreeNode>>, flag: i32) -> i32 { if flag == 2 || flag == 0 { 2 } else if flag == 1 && cur.borrow().left.is_none() { 1 } else { 3 } } fn preorder( cur: &Option<Rc<RefCell<TreeNode>>>, flag: i32, left_boundary: &mut Vec<i32>, right_boundary: &mut Vec<i32>, leaves: &mut Vec<i32>, ) { if let Some(node) = cur { if flag == 2 { right_boundary.insert(0, node.borrow().val); } else if flag == 1 || flag == 0 { left_boundary.push(node.borrow().val); } else if Self::is_leaf(node) { leaves.push(node.borrow().val); } let lf = Self::left_child_flag(node, flag); let rf = Self::right_child_flag(node, flag); let left = node.borrow().left.clone(); let right = node.borrow().right.clone(); Self::preorder(&left, lf, left_boundary, right_boundary, leaves); Self::preorder(&right, rf, left_boundary, right_boundary, leaves); } } }

::tabs-end

复杂度分析

  • 时间复杂度:$O(n)$
  • 空间复杂度:$O(n)$

其中 $n$ 为树中节点的个数。与解法一相同,单次前序遍历仍然访问每个节点一次;递归深度(或显式栈)与三个结果列表在极端情况下合计为 $O(n)$ 空间。


常见陷阱(Common Pitfalls)

1. 把叶子节点误收进左、右边界

叶子节点应当只出现一次,且只属于"叶子"部分。如果在遍历左/右边界时没有先判断isLeaf就直接把节点加入边界,叶子会在结果中重复出现:

# 错误示范:加入边界前未判断叶子 while t is not None: res.append(t.val) # 应当先判断:if not isLeaf(t) t = t.left if t.left else t.right

正确做法是加入边界前先检查if not isLeaf(t),保证非叶子才进边界。

2. 右边界顺序颠倒

右边界必须以**自下而上(bottom-to-top)**的顺序输出。常见错误是在下行遍历过程中直接把节点按自上而下的顺序加入结果,导致整体顺序错误。解法一用栈收集后统一弹出,解法二用头部插入(insert(0, ...)/unshift/add(0, ...))天然完成逆序,两者本质相同。

3. 忽略单子节点场景

左边界下行时,若某节点没有左孩子,必须沿右孩子继续(右边界反之亦然)。如果在孩子缺失处直接停止遍历,会漏掉更深处的边界节点,尤其是退化链状树场景下会严重丢节点。


两种方案对比与选型建议

维度解法一:三段拆分解法二:单次前序遍历 + 标志位
遍历次数三段独立遍历(左边界、叶子、右边界)一次前序遍历完成全部收集
右边界逆序手段显式栈 + 弹出头部插入(insert(0, ...)
叶子去重边界遍历时需手动isLeaf过滤标志位分类天然隔离,内部节点不收集
实现复杂度直观易懂,适合作为首选讲解需理解标志位传播规则,一次遍历更优雅
复杂度时间 $O(n)$,空间 $O(n)$时间 $O(n)$,空间 $O(n)$
  • 面试或教学场景推荐解法一:思路直白、代码可读性强,且与"左边界 / 叶子 / 右边界"的定义一一对应;
  • 追求单次遍历或喜欢函数式风格时可选择解法二:标志位设计把"边界身份"显式建模,逻辑更紧凑。

延伸阅读

边界遍历综合运用了前序、中序、后序三类遍历思路,也与叶子收集、树形结构判定密切相关,可继续阅读仓库中的相关文章加深理解:

  • articles/binary-tree-preorder-traversal.md —— 前序遍历的递归与迭代实现,解法二的基础;
  • articles/binary-tree-inorder-traversal.md 与 articles/binary-tree-postorder-traversal.md —— 另外两种遍历模式;
  • articles/leaf-similar-trees.md —— 叶子节点序列的收集与比较,与本文的addLeaves思路一致。

本仓库将各语言题解按目录组织(python/java/cpp/javascript/go/kotlin/swift/rust/等),本文两套解法均已给出上述语言的标准实现,可直接对照练习。

【免费下载链接】leetcodeLeetcode solutions项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询