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] ≤ 3000preorder与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 迭代 栈」类题解或视频。

浙公网安备 33010602011771号