二叉树篇
-
- [1. 二叉树的最大深度](#1. 二叉树的最大深度)
-
- Python3
-
- [写法1 ------ 递归](#写法1 —— 递归)
- [写法2 ------ 迭代](#写法2 —— 迭代)
- [2. 相同的树](#2. 相同的树)
-
- Python3
-
- [写法1 ------ 递归](#写法1 —— 递归)
- [写法2 ------ 迭代](#写法2 —— 迭代)
- [3. 翻转二叉树](#3. 翻转二叉树)
-
- Python3
-
- [写法1 ------ 递归](#写法1 —— 递归)
- [写法2 ------ 迭代](#写法2 —— 迭代)
- [4. 对称二叉树](#4. 对称二叉树)
-
- Python3
-
- [写法1 ------ 递归](#写法1 —— 递归)
- [写法2 ------ 迭代](#写法2 —— 迭代)
- [5. 从前序与中序遍历序列构造二叉树](#5. 从前序与中序遍历序列构造二叉树)
- [6. 从中序与后序遍历序列构造二叉树](#6. 从中序与后序遍历序列构造二叉树)
- [7. 填充每个节点的下一个右侧节点指针 II](#7. 填充每个节点的下一个右侧节点指针 II)
- [8. 二叉树展开为链表](#8. 二叉树展开为链表)
- [9. 路径总和](#9. 路径总和)
- [10. 求根节点到叶节点数字之和](#10. 求根节点到叶节点数字之和)
- [11. 二叉树中的最大路径和](#11. 二叉树中的最大路径和)
- [12. 二叉搜索树迭代器](#12. 二叉搜索树迭代器)
- [13. 完全二叉树的节点个数](#13. 完全二叉树的节点个数)
- [14. 二叉树的最近公共祖先](#14. 二叉树的最近公共祖先)
在二叉树的传统里,用p指代左子树,用q指代右子树,这个原因是为什么呢?我以为,将pq两个字母里的圈圈视作节点,p那一撇就是左子树的延伸,q那一捺就是右子树的延伸,所以其实是按象形的意义来命名的,这比left和right还要直观,因此,bd也可以认为是倒过来的二叉树的表示法。
1. 二叉树的最大深度
题目链接: 二叉树的最大深度 - leetcode
题目描述:给定一个二叉树
root
,返回其最大深度。二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
题目归纳:
解题思路:
解法: 二叉树的最大深度 - leetcode官方题解(1)递归,分别求出左树和右树的深度,然后返回它们之间的最大值即可
(2)迭代,这种写法比较难写
Python3
写法1 ------ 递归
python
# 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 = right
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
# 这里+1是指root本身不空,有1的深度
left_depth = self.maxDepth(root.left)+1
right_depth = self.maxDepth(root.right)+1
return max(left_depth, right_depth)
写法2 ------ 迭代
该写法实际上就是广度优先遍历
python
# 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 = right
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
# 层序遍历的次数就是最大深度
queue = [root]
depth = 0
# Searching the subtree iteratively.
while queue:
size = len(queue)
while size > 0:
temp = queue.pop(0)
if temp.left: queue.append(temp.left)
if temp.right: queue.append(temp.right)
size = size - 1
depth = depth + 1 # 同一层的节点出干净了才能depth+1
return depth
2. 相同的树
题目链接: 相同的树 - leetcode
题目描述:给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
题目归纳:
解题思路:
解法: 相同的树 - leetcode官方题解
递归
法,是深度优先 的前序遍历,先比较根节点,再比较左右子树。
迭代
法,是广度优先的层序遍历,先比较这一层,再比较该层子树。
Python3
写法1 ------ 递归
写递归的技巧,先写递归基,将一定正确和一定不正确的情况都写出来。
python
# 一、前序遍历比较
# 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 = right
class Solution:
def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
if not p and not q: # 此树(子树)一定相同
return True
elif not p or not q: # 判断到这里就是一棵树(子树)空,一棵树(子树)非空,是一定不相同的
return False
elif p.val != q.val: # 局部的正确不能代表全局的正确,所以此递归基只能判断不对,不对直接返回,对了就继续判断
return False
else: # p.val == q.val
return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
# 作者:力扣官方题解
# 链接:https://leetcode.cn/problems/same-tree/
# 来源:力扣(LeetCode)
# 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
python
# 二、中序遍历比较
# 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 = right
class Solution:
def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
if not p and not q:
return True
elif not p or not q:
return False
elif not self.isSameTree(p.left, q.left):
return False
else:
return p.val == q.val and self.isSameTree(p.right, q.right)
# 作者:力扣官方题解
# 链接:https://leetcode.cn/problems/same-tree/
# 来源:力扣(LeetCode)
# 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
python
# 三、后序遍历比较
# 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 = right
class Solution:
def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
if not p and not q:
return True
elif not p or not q:
return False
elif not self.isSameTree(p.left, q.left) or not self.isSameTree(p.right, q.right):
return False
else:
return p.val == q.val
# 作者:力扣官方题解
# 链接:https://leetcode.cn/problems/same-tree/
# 来源:力扣(LeetCode)
# 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
写法2 ------ 迭代
python
# 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 = right
class Solution:
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
if not p and not q:
return True
if not p or not q:
return False
# Iteration
# 1. Creating two queues to store TreeNodes from p and q
queue_p = [p]
queue_q = [q]
# 2. Comparing TreeNodes in p and q
while queue_p and queue_q:
tmp_p = queue_p.pop(0)
tmp_q = queue_q.pop(0)
if tmp_p == None and tmp_q == None: # None值提前判断
continue
elif tmp_p == None or tmp_q == None:
return False
if(tmp_p.val == tmp_q.val): # 比较该层节点再比较子树
queue_p.append(tmp_p.left)
queue_p.append(tmp_p.right)
queue_q.append(tmp_q.left)
queue_q.append(tmp_q.right)
else:
return False
return True
3. 翻转二叉树
题目链接: 翻转二叉树 - leetcode
题目描述:给你一棵二叉树的根节点
root
,翻转这棵二叉树,并返回其根节点。
题目归纳:可理解为镜面对称,在根节点垂直画一条线,左右子树以此线为轴镜面对称
解题思路:
解法: 翻转二叉树 - leetcode官方题解(1)递归。
(2)迭代。
Python3
写法1 ------ 递归
python
# 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 = right
class Solution:
# 这里的这个Optional[ClassName]是什么意思?
# option是名词,意思为选项,optional是一个形容词,意思为可选择的
# 参考1:https://docs.python.org/3/library/typing.html?highlight=optional#typing.Optional
# 参考2:https://www.python100.com/html/IP6IC27G625Y.html
# 很多python源码都有大量使用Optional
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
# 解题思路
# 1.若节点为None,返回原值None
# 2.若节点不为空,交换节点的左右子节点
if root == None:
return root
root.left, root.right = self.invertTree(root.right), self.invertTree(root.left)
return root
写法2 ------ 迭代
上面的递归 写法,是深度优先 遍历,走到树的最底层,先交换最底层的叶节点,再依次往上交换 。
下面的迭代 写法,是广度优先 遍历,走到树的每一层,先交换该层的左右子树,再依次往下交换 。
由此,广度优先和深度优先效果上是等价的,是可以互相转换的,这一点是需要记住的。
骈赋汉韵,重章叠唱,诗经风雅,代码亦然。
python
# 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 = right
class Solution:
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if root == None:
return None
# 1.将节点逐层放入队列
queue = [root]
while queue:
tmp = queue.pop(0) # 一定要带上0,才是从队首出队,否则是从队尾
tmp.left, tmp.right = tmp.right, tmp.left # 2.交换左右子树
if tmp.left: # 3.逐层往下
queue.append(tmp.left)
if tmp.right:
queue.append(tmp.right)
return root # 4.Done,返回
# 作者:王尼玛
# 链接:https://leetcode.cn/problems/invert-binary-tree/
# 来源:力扣(LeetCode)
# 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
4. 对称二叉树
题目链接: 对称二叉树 - leetcode
题目描述:给你一个二叉树的根节点 root , 检查它是否轴对称。
题目归纳:Leetcode编排的这套题目很有趣,是一环套一环,在之前题目的基础上再拓展思路,就可以组合成一道新的题目,这道题目就是拿上面的题目2和题目3组合出来的,如果 一棵树经过翻转之后,和原来的树一模一样,那么这棵树就是对称二叉树,先用到了题目3的解题思路,再用到了题目2的解题思路,理论上,我们可以创建一棵新的和A一模一样的树B,然后翻转B,再与A比较即可,当然,这在实际中没一点用,但思路这样没错。
解题思路:
解法: 对称二叉树 - leetcode官方题解(1) 递归法。题目2的解法是左子树比左子树,右子树比右子树,那么这道题就只要交换下顺序,左子树比右子树,右子树比左子树,效果和创建新树B,再翻转树B与A比较是一样的。
(2) 迭代法。递归比较左右子树法容易想到,但平均来说更消耗空间,也可以采用迭代法。
Python3
写法1 ------ 递归
python
# 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 = right
def isSameTree(p: TreeNode, q: TreeNode) -> bool:
if not p and not q:
return True
elif not p or not q:
return False
elif p.val != q.val:
return False
else:
return isSameTree(p.left, q.right) and isSameTree(p.right, q.left) # 借用了题目2但有区别
class Solution:
def isSymmetric(self, root: Optional[TreeNode]) -> bool:
if root == None:
return True
p = root.left
q = root.right
# Based on the solution of 2nd question.
if p == None and q == None:
return True
if p == None or q == None:
return False
if p.val != q.val:
return False
return isSameTree(p.left, q.right) and isSameTree(p.right, q.left)
写法2 ------ 迭代
python
# 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 = right
class Solution:
def isSymmetric(self, root: Optional[TreeNode]) -> bool:
if root == None:
return True
p = root.left
q = root.right
if p == None and None == q:
return True
elif p == None or None == q:
return False
queue_p = [p]
queue_q = [q]
LRlocat_p = ['left'] # locating the node where is catch from, left or right.
LRlocat_q = ['right'] # 左右位置的标记
# Hierarchical traversal method
while queue_p and queue_q:
temp_p = queue_p.pop(0)
temp_q = queue_q.pop(0)
locat_p = LRlocat_p.pop(0)
locat_q = LRlocat_q.pop(0)
if(temp_p != None and None != temp_q and temp_p.val == temp_q.val and locat_p != locat_q):
queue_p.append(temp_p.left)
LRlocat_p.append('left')
queue_p.append(temp_p.right)
LRlocat_p.append('right')
queue_q.append(temp_q.right) # Attention!!
LRlocat_q.append('right')
queue_q.append(temp_q.left)
LRlocat_q.append('left')
elif temp_p == None and None == temp_q and locat_p != locat_q:
continue
else:
return False
return True
5. 从前序与中序遍历序列构造二叉树
题目链接: 从前序与中序遍历序列构造二叉树 - leetcode
题目描述:给定两个整数数组
preorder
和inorder
,其中preorder
是二叉树的先序遍历,inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
preorder 和 inorder 均 无重复 元素
题目归纳:
给定一棵二叉树的前序和中序遍历结果,可以唯一确定一棵二叉树。
解题思路:
解法: 从前序与中序遍历序列构造二叉树 - leetcode官方题解(1) 递归法。
(2) 迭代法。
python
# 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 = right
class Solution:
# 递归法
def myBuildTree(self, preorder, index, preorder_left: int, preorder_right: int, inorder_left: int, inorder_right: int) -> Optional[TreeNode]:
# preorder_left: 想要构造的 前序遍历数组 的 左边界 位置
# preorder_right:想要构造的 前序遍历数组 的 右边界 位置
# inorder_left: 想要构造的 中序遍历数组 的 左边界 位置
# inorder_right: 想要构造的 中序遍历数组 的 右边界 位置
if preorder_left > preorder_right: # 该区间构造完成
return None
# 前序遍历中的第一个节点,就是本次根节点
preorder_root = preorder_left
# 在中序遍历中,定位本次根节点索引位置
preorder_val = preorder[preorder_root]
inorder_root = index[preorder_val] # 在中序遍历中,定位本次根节点索引位置
# 建立本次的根节点
root = TreeNode(preorder_val)
# 得到中序遍历结果中,左子树的节点数目
size_left_subtree = inorder_root - inorder_left
# (1)递归构造左子树
root.left = self.myBuildTree(preorder, index, preorder_left+1, preorder_left+size_left_subtree, inorder_left, inorder_root-1)
# (2)递归构造右子树
root.right = self.myBuildTree(preorder, index, preorder_left+size_left_subtree+1, preorder_right, inorder_root+1, inorder_right)
return root
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
# 给定两个整数数组,分别是preorder和inorder,即前序和中序遍历结果,请构造二叉树并返回其root节点
# (1)给定前序遍历和中序遍历,可以唯一确定一棵二叉树,例如:
# root-[left_result]-[right-result] 为前序遍历结果
# [left_result]-root-[right-result] 为中序遍历结果
# 那么取前序的第一个节点root,再去中序中定位root节点,即可确定唯一的左子树和右子树
# (2)每当要提高查找速度时,都可以联想到hash表这个数据结构,对于本题而言,构造一个hash表,其键值对 key-value 为 节点值-该值在中序遍历的出现位置
# 这样在构造二叉树的过程中,就只需要O(1)的时间对根节点进行定位了
n = len(preorder)
# 方便根据val查找索引位置。题目中说了 preorder 和 inorder 均 无重复 val元素,如果有重复val元素,还要比较左子树和右子树的遍历结果长度
index = { node_val:i for i,node_val in enumerate(inorder)}
return self.myBuildTree(preorder, index, 0, n-1, 0, n-1)
6. 从中序与后序遍历序列构造二叉树
题目链接: 从中序与后序遍历序列构造二叉树 - leetcode
题目描述:给定两个整数数组
inorder
和postorder
,其中inorder
是二叉树的中序遍历,postorder
是同一棵树的后序遍历,请构造二叉树并返回其根节点。输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]
题目归纳:
给定一棵二叉树的后序和中序遍历结果,可以唯一确定一棵二叉树。与第5题过程反着来即可。
解题思路:
解法: 从中序与后序遍历序列构造二叉树 - leetcode官方题解(1) 递归法。
(2) 迭代法。
python
# 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 = right
class Solution:
def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
def myBuildTree(postorder_left: int, postorder_right: int, inorder_left: int, inorder_right: int) -> Optional[TreeNode]:
# preorder_left: 想要构造的 后序遍历数组 的 左边界 位置
# preorder_right:想要构造的 后序遍历数组 的 右边界 位置
# inorder_left: 想要构造的 中序遍历数组 的 左边界 位置
# inorder_right: 想要构造的 中序遍历数组 的 右边界 位置
if postorder_left > postorder_right: # 该区间构造完成
return None
# 后序遍历中的最后一个节点,就是本次根节点
postorder_root = postorder_right
# 在中序遍历中,定位本次根节点索引位置
postorder_val = postorder[postorder_root]
inorder_root = index[postorder_val] # 在中序遍历中,定位本次根节点索引位置
# 建立本次的根节点
root = TreeNode(postorder_val)
# 得到中序遍历结果中,右子树的节点数目
size_right_subtree = inorder_right - inorder_root
# (1)递归构造右子树,与第5题相反
root.right = myBuildTree( postorder_right-size_right_subtree, postorder_right-1, inorder_root+1, inorder_right)
# (2)递归构造左子树,与第5题相反
root.left = myBuildTree( postorder_left, postorder_right-size_right_subtree-1, inorder_left, inorder_root-1)
return root
n = len(inorder)
index = {element:i for i, element in enumerate(inorder)}
return myBuildTree(0, n-1, 0, n-1)
7. 填充每个节点的下一个右侧节点指针 II
题目链接: 填充每个节点的下一个右侧节点指针 II - leetcode
题目描述:填充它的每个
next
指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将next
指针设置为NULL
。初始状态下,所有next
指针都被设置为NULL
。输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]
题目归纳:
解题思路:
解法: 填充每个节点的下一个右侧节点指针 II - leetcode官方题解
python
"""
# Definition for a Node.
class Node:
def __init__(self, val: int = 0, left: 'Node' = None, right: 'Node' = None, next: 'Node' = None):
self.val = val
self.left = left
self.right = right
self.next = next
"""
"""
1 -> null
/ \
2 -> 3 -> null
/ \ \
4 -> 5 -> 7 -> null
上面这个图是本题的示意图,最终的next指针是要连接成上面这个样子的,这种结构在B+树中很有用,B+树的底层叶子节点就是这样连起来的
https://en.wikipedia.org/wiki/B%2B_tree
若第i层已建立next指针连接,那么就可以通过left与right指针知道第i+1层的孩子节点,从而为第i+1层节点建立next指针连接
即在第i层的时候为第i+1层建立next指针连接,依次类推
(1)第一队节点:most_left_node。用来指向每一层的最左节点
(2)第二队节点:P。遍历该层每一个节点
(3)第三队节点:self.last, self.nextLayer_most_left_node,要求全局可读可写,所以写成self。用来在connect_next函数中构建next关系。
"""
class Solution:
def connect(self, root: 'Node') -> 'Node':
if not root: # 空节点
return None
most_left_node = root
while most_left_node:
# print("most_left_node.val", most_left_node.val)
self.last = None
self.nextLayer_most_left_node = None
P = most_left_node
while P: # 遍历本层节点
if P.left:
self.connect_next(P.left) # 对下一层节点构建next
if P.right:
self.connect_next(P.right) # 对下一层节点构建next
P = P.next
most_left_node = self.nextLayer_most_left_node
return root
def connect_next(self, p): # 官解的命名不好,应该和上面的p命名区分开来,p是P的子节点
if self.last:
self.last.next = p
if not self.nextLayer_most_left_node: # 这种情况发生时,说明传入的p本身就是该层最左侧节点
self.nextLayer_most_left_node = p
self.last = p
8. 二叉树展开为链表
题目链接: 二叉树展开为链表 - leetcode
题目描述:给定两个整数数组
preorder
和inorder
,其中preorder
是二叉树的先序遍历,inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。题目归纳:
解题思路:
解法: 二叉树展开为链表 - windliang民间题解
python
# 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 = right
class Solution:
def flatten(self, root: Optional[TreeNode]) -> None:
"""
Do not return anything, modify root in-place instead.
"""
# (1)把左子树插入到右子树的地方
# (2)然后将原来的右子树,接到(1)的左子树的最右边节点
# (3)重复上述过程,直到所有左子树为空
while root:
if not root.left: # 左子树为None,考虑下一个节点
root = root.right
else: # 存在左子树,需要将左子树插入到右子树的地方
# 暂存右子树
temp_right = root.right
# root的右指针指向左子树
root.right = root.left
# 断开左子树
root.left = None
# 找到现在成为右子树的'左子树'的最右节点,并续上temp_right
pre = root.right
while pre.right:
pre = pre.right
pre.right = temp_right
# 考虑下一个节点
root = root.right
9. 路径总和
题目链接: 路径总和 - leetcode
题目描述:给你二叉树的根节点
root
和一个表示目标和的整数targetSum
。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和targetSum
。如果存在,返回true
;否则,返回false
。叶子节点 是指没有子节点的节点。
题目归纳:
解题思路:
解法: 路径总和 - leetcode官方题解找到一条路径然后把路径上的节点值之和sum求出来,并与targetSum对比。
递归法求解。
python
# 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 = right
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
# 给你二叉树的根节点root,和一个表示目标和的整数targetSum
# 判断该树中是否存在根节点到叶节点的路径,且这条路径上所有节点值相加= targetSum
# 若存在返回true
# 考虑递归解法
# 将问题从大问题转化为小问题:是否存在当前节点的child节点到叶子的路径,使其满足路径和为sum-val,那么有:
# (1)若当前节点为叶子节点,判断sum是否等于val
# (2)若当前节点不是叶子节点,递归查询其子节点能否满足条件
if not root: # 空节点
return False
if not root.left and not root.right: # 叶结点
return targetSum == root.val
return self.hasPathSum(root.left, targetSum - root.val) or self.hasPathSum(root.right, targetSum - root.val) # 查找子节点
10. 求根节点到叶节点数字之和
题目链接: 求根节点到叶节点数字之和 - leetcode
题目描述:给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。
每条从根节点到叶节点的路径都代表一个数字:
例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123 。
计算从根节点到叶节点生成的 所有数字之和 。
叶节点 是指没有子节点的节点。
题目归纳:
解题思路:
解法: 求根节点到叶节点数字之和 - leetcode官方题解深搜法求解
python
# 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 = right
class Solution:
def sumNumbers(self, root: Optional[TreeNode]) -> int:
# 这似乎是一种节省空间的存储方式,就像前缀树,然后求和的时候是以时间换空间
def dfs(root: Optional[TreeNode], prevSum: int) -> int:
if not root:
return 0
Sum = prevSum * 10 + root.val
if not root.left and not root.right: # 叶节点
return Sum
else: # 继续递归
return dfs(root.left, Sum) + dfs(root.right, Sum)
return dfs(root, 0)
11. 二叉树中的最大路径和
题目链接: 二叉树中的最大路径和 - leetcode
题目描述:二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点
root
,返回其 最大路径和 。
题目归纳:看文字不明白请看官方的图示。这更像是一道图论题。
解题思路:
解法: 二叉树中的最大路径和 - leetcode官方题解
python
# 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 = right
# 递归法求解
# 考虑一个函数maxGain(node),该函数会计算此节点的最大贡献值。什么是最大贡献值?就是在以该节点为根节点的子树中,寻找以该节点为起点的一条路径,使该路径上的节点值之和最大
# maxGain计算规则:
# (A)空节点最大贡献值为0
# (B)非空节点最大贡献值 = 本节点值 + 其子节点中最大贡献值(>0)(因此对于叶子节点,其最大贡献值为其自身节点值)
# 在得到上述的最大贡献值之后,可以着手计算最大路径和,请注意,最大贡献值与最大路径和这两个概念并不是一回事!
# 对于二叉树中的一个节点,该节点的最大路径和 = max(左子树根节点最大贡献值(要>0),右子树根节点最大贡献值(要>0)),并且贡献值之所以叫贡献值,就是要>0才叫贡献
# 用一个全局变量maxSum,在递归过程中更新最大路径和即可。
class Solution:
def __init__(self):
self.maxSum = float("-inf")
def maxPathSum(self, root: Optional[TreeNode]) -> int:
def maxGain(node: Optional[TreeNode]) -> int:
if not node: return 0 # 最大贡献值为0
# 递归计算左右子节点的最大贡献值
# 只有在最大贡献值大于 0 时,才会选取对应子节点
leftGain = max(0, maxGain(node.left))
rightGain = max(0, maxGain(node.right))
# 节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值
priceNewPath = node.val + leftGain + rightGain
self.maxSum = max(self.maxSum, priceNewPath) # 更新答案
# 返回节点最大贡献值
return node.val + max(leftGain, rightGain)
maxGain(root)
return self.maxSum
12. 二叉搜索树迭代器
题目链接: 二叉搜索树迭代器 - leetcode
题目描述:实现一个二叉搜索树迭代器类
BSTIterator
,表示一个按中序遍历二叉搜索树(BST
)的迭代器:
BSTIterator(TreeNode root)
初始化 BSTIterator 类的一个对象。BST
的根节点root
会作为构造函数的一部分给出。指针应初始化为一个不存在于BST
中的数字,且该数字小于BST
中的任何元素。
boolean hasNext()
如果向指针右侧遍历存在数字,则返回true
;否则返回false
。
int next()
将指针向右移动,然后返回指针处的数字。注意,指针初始化为一个不存在于
BST
中的数字,所以对 next() 的首次调用将返回BST
中的最小元素。你可以假设next()
调用总是有效的,也就是说,当调用next()
时,BST
的中序遍历中至少存在一个下一个数字。
题目归纳:
解题思路:
解法: 二叉搜索树迭代器 - leetcode官方题解利用数组保存中序遍历结果,最后利用得到的数组,来实现和完成这个迭代器。
python
# 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 = right
# (A)二叉搜索树满足该特性:左节点 < root < 右节点
# (B)该迭代器要按中序遍历返回值。
# (1)先对二叉搜索树做一次完全的递归遍历,并将中序遍历的全部结果保存在数组中。最后利用得到的数组,来实现和完成这个迭代器。
class BSTIterator:
def __init__(self, root: Optional[TreeNode]):
self.idx = 0
self.arr = []
self.inorderTraversal(root, self.arr) # 将root的中序遍历结果保存在arr中
def next(self) -> int: # 题目说了假设next调用都是有效的,因此不用考虑数组越界
node = self.arr[self.idx]
print(node)
self.idx += 1
return node
def hasNext(self) -> bool:
return self.idx < len(self.arr)
def inorderTraversal(self, root: Optional[TreeNode], arr: list):
if not root:
return
self.inorderTraversal(root.left, arr)
arr.append(root.val)
self.inorderTraversal(root.right, arr)
# Your BSTIterator object will be instantiated and called as such:
# obj = BSTIterator(root)
# param_1 = obj.next()
# param_2 = obj.hasNext()
13. 完全二叉树的节点个数
题目链接: 完全二叉树的节点个数 - leetcode
题目描述:给你一棵 完全二叉树 的根节点
root
,求出该树的节点个数。完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第h
层,则该层包含 1~ 2^h^ 个节点。
题目归纳:
解题思路:
解法: 完全二叉树的节点个数 - leetcode官方题解(1) 暴力遍历节点计数。计算二叉树节点个数的通用方法,但未利用到完全二叉树的特性,时间复杂度有 O ( n ) O(n) O(n),因此不推荐在本题中使用。
(2) 利用完全二叉树的特性。二分查找+位运算,我自认为我的代码比官方注释的更详细。
python
# 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 = right
class Solution:
def countNodes(self, root: Optional[TreeNode]) -> int: # 暴力搜索
if not root: # 空节点
return 0
return self.countNodes(root.left) + self.countNodes(root.right) + 1 # +1是指自身
python
# 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 = right
# 利用完全二叉树的特性计算节点个数,规定:根节点位于第0层,完全二叉树的最大层数为h,根据完全二叉树的特性,最左节点一定在第h层。
# (A)当最底层h包含 1 个节点时,完全二叉树的节点个数是 2^h 个,记该情况个数为A
# (B)当最底层h包含 2^h 个节点时,完全二叉树的节点个数是 2^h + (2^h - 1) = 2^(h+1) - 1 个,记该情况个数为B
# 因此,对于最大层数为 h 的完全二叉树,节点个数一定在 [A,B] 的范围内,可以在该范围内通过二分查找的方式,得到完全二叉树的节点个数,做法如下
# 根据节点个数范围上下界,得到当前需要判断的节点个数k
# (1)如果第k个节点存在, 则节点个数一定 >= k
# (2)如果第k个节点不存在, 则节点个数一定 < k
# 由此可以将查找范围缩小一半,直到得到节点个数,但是如何判断第k个节点是否存在?
# 若第k个节点位于第h层,则[k的二进制数字]表示包含h+1位,其中最高位是1,其余各位从高到低表示从根节点root到第k个节点的路径,0表示向左,1表示向右。因此可以通过位运算得到第k个节点对应的路径,从判断该路径对应的节点存在性,即可判断第k个节点是否存在
# wow!这个思路借鉴了哈夫曼编码
class Solution:
def exists(self, node: Optional[TreeNode], h: int, k: int): # h为层数,k为节点序号
route_mask = 1 << (h - 1) # 第h层的二进制表达,即路径掩码,route_mask始终只有最高位为1。这里注意,算法不会传入h=0,由h_left < h_right保证。
while node and route_mask > 0:
if (route_mask & k) == 0: # 与route_mask的最高位进行与&操作,判断向左还是向右
node = node.left
else:
node = node.right
route_mask >>= 1 # 要判断下一层路径,向右移位
return node # node不空表明存在
def countNodes(self, root: Optional[TreeNode]) -> int:
if not root: return 0
# (1)寻找树的层数。时间复杂度O(h)=O(log2^h),而 2^h<=n<=2^(h+1),因此O(h)=O(log n)
h = 0 # 起始的根节点层数为0
p = root
while p.left:
h += 1
p = p.left
# (2)二分查找。
h_left = 1 << h # 第h层最左节点的序号
h_right = (1 << (h+1)) - 1 # 第h层最右节点的序号
while h_left < h_right:
# 如何求mid是个问题,但为什么官方题解要+1呢?int mid = (high - low + 1) / 2 + low;
# 原因在于,当两个结点按序紧挨着时,+1能让mid跨过向下取整的阻碍,所以括号(h_left+h_right+2)里这样+2也是可以的,这等价于括号外整体+1
# 比如(11+12)//2 = 11,后面永远会是(11+12)//2,从而造成死循环,(11+12+1)//2 = 12,这样就能让12赋值到mid变量中,相当于给mid一个额外的向右走的动力,即便走多了也会被h_right = h_mid - 1拉回来,说穿了这就是个步进精度的问题,只是特定时刻步进精度会受制于向下取整这个操作,现在可以理解为什么梯度下降在深度学习中这么受欢迎了,查找绝对是门技术活
h_mid = (h_left + h_right) // 2 + 1
if self.exists(root, h, h_mid):
h_left = h_mid # 存在h_mid不代表存在h_mid+1
else:
h_right = h_mid - 1 # 存在h_mid一定存在h_mid-1
return h_left
14. 二叉树的最近公共祖先
题目链接: 二叉树的最近公共祖先 - leetcode
题目描述:给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:"对于有根树
T
的两个节点p、q
,最近公共祖先表示为一个节点x
,满足x
是p、q
的祖先且x
的深度尽可能大(一个节点也可以是它自己的祖先)。"
题目归纳:仅对于我们自身而言,中国人的直系族谱(不考虑伯伯叔叔,七大姑八大姨的话)是一棵倒着的二叉树,如何查找任意两个中国人的公共祖先呢?这可以等价为查找两棵二叉树的公共子节点,不然每当有人想说:"替我问候你的祖先"时,岂不是还得考虑可能问候到自己的祖先。
解题思路:
解法: 二叉树的最近公共祖先 - Krahets民间题解
python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
# 最近公共祖先的定义为, 对于有根树T的两个节点p、q,最近公共祖先是一个节点x。
# 其中,x是p、q的祖先,且x的深度尽可能大(一个节点也可以是它自己的祖先),记住深度尽可能大。
# 另外,p,q应该是存在于树中的,而非拷贝的TreeNode
# 可以这样定义祖先: 若节点p在节点root的左(右)子树中,或p==root即root本身,则称root是p的祖先
# 可以这样定义最近公共祖先:设节点root为节点p,q的某公共祖先,若root.left和root.right都不是p,q的公共祖先,则称root是最近的公共祖先(即不可能再从root往下找到更近的祖先)
if not root: return None
# (1)root==p时,root再往下绝对不可能是p自己的祖先,但p可能是q的祖先
# (2)root==q时,root再往下绝对不可能是q自己的祖先,但q可能是p的祖先
# 所以直接返回root,因为祖先节点可以是自己
if root == p or root == q:
return root
left = self.lowestCommonAncestor(root.left, p, q) # 去root.left树找p、q的公共祖先
right = self.lowestCommonAncestor(root.right, p, q) # 去root.right树找p、q的公共祖先
if not left: return right # 左边没有返回右边
if not right: return left # 右边没有返回左边
return root # 两边都找到了公共祖先的话,只可能是找到的各自是p,q自己,所以root自己是公共祖先