Leetcode 二叉树 5

题号直达: 199 二叉树的右视图 | 114 二叉树展开为链表 | 105 从前序与中序遍历序列构造二叉树

LeetCode 199. 二叉树的右视图 ↑

题目

给定一个二叉树的根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的结点值。

提示

  • 二叉树的结点个数在范围 [0, 100] 内
  • -100 ≤ Node.val ≤ 100

代码(Python)解法一:层序遍历(BFS)+ 每层取最右

from collections import deque

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution(object):
    def rightSideView(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: List[int]
        """
        if not root:
            return []
        p = deque()
        p.append(root)
        ans = []
        while p:
            N = len(p)
            temp = []
            for i in range(N):
                node = p.popleft()
                temp.append(node.val)
                if node.left:
                    p.append(node.left)
                if node.right:
                    p.append(node.right)
            ans.append(temp)

        final = []
        for i in range(len(ans)):
            final.append(ans[i][-1])
        return final

思路解析(解法一)

  • 右视图的含义:站在右侧时,每一层最靠右的那个结点会被看到(若某层只有左子树一侧有结点,则该层「最右可见」由该层最后一个被 BFS 访问到的结点决定,本题按层序从左到右扩展,每层最后一个入队顺序上即为该层最右结点)。
  • 实现:用 deque 做层序遍历(与 102 题类似,按层处理:先记本层宽度 N,再 popleft 恰好 N 次)。每层把访问到的 node.val 记入 temp,再将本层结点对应的孩子入队;一层结束后将 temp 整层加入 ans。
  • 得到右视图:每一层在视野上最右侧的值即该层列表的最后一个元素 temp[-1],对所有层取 ans[i][-1] 得到 final。
  • 可简化:不必先存整层 temp 再取末元素,可在本层循环内用变量仅记录当前层最后一个 node.val,每层结束后 append 一次,少一层列表与二次遍历。
  • 复杂度:每个结点入队、出队各一次,时间 O(n),n 为结点数;队列最坏 O(n)。

代码(Python)解法二:DFS + 深度哈希(先右后左的访问顺序)

用栈模拟深度优先:对每个深度 depth,第一次写入哈希表的值即为该层最先被访问的结点;将左孩子先入栈、右孩子后入栈,则出栈时先走右子树,保证同一深度上先记录的是最右侧结点。setdefault(depth, node.val) 仅在 depth 尚未出现时写入,避免被同层左侧结点覆盖。

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution(object):
    def rightSideView(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: List[int]
        """
        rightmost_value_at_depth = {}
        max_depth = -1
        stack = [(root, 0)]
        while stack:
            node, depth = stack.pop()
            if node is not None:
                max_depth = max(max_depth, depth)
                rightmost_value_at_depth.setdefault(depth, node.val)

                stack.append((node.left, depth + 1))
                stack.append((node.right, depth + 1))

        return [rightmost_value_at_depth[depth] for depth in range(max_depth + 1)]

思路解析(解法二)

  • 与解法一的区别:不显式按层队列,而是用深度作为键,在 DFS 过程中为每一层保留一个代表值。
  • 栈的顺序:append 先左后右,弹出时先处理右再处理左,因此同一深度上最先被访问的是该层从右侧看应保留的结点。
  • dict.setdefault(depth, node.val):若键 depth 尚不存在,则执行 rightmost_value_at_depth[depth] = node.val;若已存在,则什么也不做(不会覆盖)。这样每个深度只保留第一次写入的值,与「先右后左」的访问顺序配合,即得到该层最右可见值。
  • max_depth:记录树的最大深度,最后按 0 … max_depth 顺序拼出列表,保证自上而下与题目「从顶到底」一致。
  • 空树:root 为 None 时栈中仅 (None, 0),不进入 if node is not None,max_depth 仍为 -1,range(0) 为空,返回 []。
  • 复杂度:时间 O(n);哈希表深度最多 O(n) 层,栈最坏 O(n)。

LeetCode 114. 二叉树展开为链表 ↑

题目

给定一个二叉树的根节点 root,将其展开为一棵「链表」:

  • 展开后的「链表」仍使用原来的 TreeNode,其中 right 指针表示链表中的下一个结点,left 指针始终为 null;
  • 展开顺序应与二叉树的先序遍历(根 → 左 → 右)一致。

提示

  • 树中结点数在范围 [0, 2000] 内
  • -100 ≤ Node.val ≤ 100

进阶:能否使用原地算法,且额外空间为 O(1)(不含递归栈时指常数辅助空间)完成展开?

代码(Python)解法一:先序收集结点 + 再串成链

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution(object):
    def __init__(self):
        self.res = []

    def pre(self, root):
        if root is None:
            return None
        self.res.append(root)
        self.pre(root.left)
        self.pre(root.right)

    def flatten(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: None Do not return anything, modify root in-place instead.
        """
        if not root:
            return None
        self.res = []
        self.pre(root)
        p = self.res[0]
        for i in range(len(self.res) - 1):
            p.right = self.res[i + 1]
            p.left = None
            p = p.right
        p.left = None
        p.right = None
        return None

思路解析(解法一)

  • 两步走:先用 pre 做先序 DFS,把结点引用依次存入 self.res;再线性扫描,令 p.right 指向下一个结点、p.left = None,最后在链表尾将 right 置空。
  • self.res = []:每次 flatten 前清空列表,避免多次调用时残留上次遍历结果。
  • 空树:root 为空时直接返回,符合原地修改约定。
  • 与进阶的关系:本题解额外使用 O(n) 的结点列表(及递归栈 O(h)),不满足进阶 O(1) 额外空间;进阶常采用迭代拆左子树接到右子树一侧、或 Morris 式遍历等原地调整指针的写法。
  • 返回值:LeetCode 接口要求 flatten 无返回值(仅修改树),故末尾写 return None;若本地调试曾写 return self.res[0],提交时需去掉。

代码(Python)解法二:原地迭代(O(1) 额外空间)

思路:自顶向下,若当前结点有左子树,则找到左子树中最右结点 pre(在原树中序里位于当前根之前的最后一个结点);将原右子树接到 pre.right,再把整棵左子树挪到 curr.right,左指针置空;curr 沿新的 right 走向先序中的下一个结点。无左子树时直接沿 right 下移。全程仅用若干指针变量,满足进阶 O(1) 辅助空间(迭代、无列表)。

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution(object):
    def flatten(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: None Do not return anything, modify root in-place instead.
        """
        if not root:
            return None
        curr = root
        while curr.right or curr.left:
            if not curr.left:
                curr = curr.right
                continue
            nex = curr.left
            pre = nex
            while pre.right:
                pre = pre.right
            pre.right = curr.right
            curr.left = None
            curr.right = nex
            curr = curr.right
        return None

视频讲解(解法二)

思路解析(解法二)

  • 核心:每次处理「有左子树」的结点时,把原右子树接到左子树最右结点之后,再把左子树整体换到当前结点的右孩子位置,并清空 left。这样先序顺序被逐步「拉直」到右链上。
  • pre 的含义:在左子树内不断 pre = pre.right,直到无右孩子,即为左子树的最右结点;在原树中它恰是先序里紧挨当前根之后、进入左子树一路向右能走到的最后一个结点,适合承接原根的整棵右子树。
  • 循环条件 while curr.right or curr.left:当前还未走完「待展开」部分;若只有右链、无左子树,则 curr = curr.right 继续向尾部推进。
  • 与解法一对比:不存 self.res,时间仍为 O(n)(每个结点被访问常数次),额外 O(1) 指针空间。
  • 返回值:接口要求无返回值,写作 return None;若写 return root 仅便于本地查看,提交时应去掉或改为 None。

LeetCode 105. 从前序与中序遍历序列构造二叉树 ↑

题目

给定两个整数数组 preorder 与 inorder,其中 preorder 是某二叉树的先序遍历,inorder 是同一棵树的中序遍历,请构造该二叉树并返回其根结点。

提示

  • 1 ≤ preorder.length ≤ 3000,且 len(inorder) == len(preorder)
  • -3000 ≤ preorder[i], inorder[i] ≤ 3000
  • preorder 与 inorder 均无重复元素,且 inorder 中的值均出现在 preorder 中
  • 保证 preorder、inorder 分别对应某二叉树的合法先序、中序遍历序列

代码(Python)解法一:递归分治 + 中序下标哈希

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution(object):
    def buildTree(self, preorder, inorder):
        """
        :type preorder: List[int]
        :type inorder: List[int]
        :rtype: Optional[TreeNode]
        """
        def myBuildTree(preorder_left, preorder_right, inorder_left, inorder_right):
            if preorder_left > preorder_right:
                return None

            preorder_root = preorder_left
            inorder_root = index[preorder[preorder_root]]

            root = TreeNode(preorder[preorder_root])
            size_left_subtree = inorder_root - inorder_left
            # 左子树先序:[preorder_left+1, preorder_left+size_left_subtree]
            # 左子树中序:[inorder_left, inorder_root-1]
            root.left = myBuildTree(
                preorder_left + 1,
                preorder_left + size_left_subtree,
                inorder_left,
                inorder_root - 1,
            )
            # 先序余下区间与中序 [inorder_root+1, inorder_right] 对应右子树
            root.right = myBuildTree(
                preorder_left + size_left_subtree + 1,
                preorder_right,
                inorder_root + 1,
                inorder_right,
            )
            return root

        n = len(preorder)
        # 值 -> 中序下标,O(1) 定位根
        index = {element: i for i, element in enumerate(inorder)}
        return myBuildTree(0, n - 1, 0, n - 1)

思路解析(解法一)

  • 根结点:先序区间 [preorder_left, preorder_right] 中,第一个元素 preorder[preorder_left] 即为当前子树根。
  • 在中序中切分左右子树:用哈希表 index(值 → 中序下标)在 O(1) 内定位根在中序中的位置 inorder_root。中序上,[inorder_left, inorder_root - 1] 为左子树中序,[inorder_root + 1, inorder_right] 为右子树中序;左子树结点个数 size_left_subtree = inorder_root - inorder_left。
  • 先序区间对应关系:先序里紧跟根的 size_left_subtree 个结点为左子树先序,余下为右子树先序,从而得到递归的四组下标边界。
  • 递归出口:当 preorder_left > preorder_right(或等价地中序区间为空)时,子树为空,返回 None。
  • 为何可无重复元素:保证 index 中每个值唯一,与中序划分一一对应。
  • 复杂度:每个结点建一次,时间 O(n);哈希表 O(n),递归栈最坏 O(n)(链状树)。

代码(Python)解法二:迭代 + 栈

说明(个人备注):下面为迭代写法,用栈与中序扫描指针配合构造;暂时不打算深入理解,仅作存档,日后需要时再对照官方题解或动画逐步啃。

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution(object):
    def buildTree(self, preorder, inorder):
        if not preorder:
            return None

        root = TreeNode(preorder[0])
        stack = [root]
        inorder_index = 0

        for i in range(1, len(preorder)):
            preorder_val = preorder[i]
            node = stack[-1]

            if node.val != inorder[inorder_index]:
                node.left = TreeNode(preorder_val)
                stack.append(node.left)
            else:
                while stack and stack[-1].val == inorder[inorder_index]:
                    node = stack.pop()
                    inorder_index += 1
                node.right = TreeNode(preorder_val)
                stack.append(node.right)

        return root

思路解析(解法二)

  • 定位:与解法一的「递归分治 + 哈希」不同,本写法在一条扫描先序序列的过程中,用 栈 维护当前路径,并用 inorder_index 对齐中序序列,在「栈顶与中序当前值可匹配」时弹出并右扩,否则向左下接新结点;属于常见 O(n) 时间、O(n) 栈空间的迭代模板。
  • 为何不再展开:见上方备注,暂不逐行推导;需要时可检索「105 迭代 栈」类题解或视频。
posted @ 2026-10-12 00:04  Phier  阅读(2)  评论(0)    收藏  举报