一、树?
树 是一种经常用到的数据结构,用来模拟具有树状结构性质的数据集合。
树里的每一个节点有一个值和一个包含所有子节点的列表。从图的观点来看,树也可视为一个拥有N 个节点和N-1 条边的一个有向无环图。
二叉树是一种更为典型的树状结构。如它名字所描述的那样,二叉树是每个节点最多有两个子树的树结构,通常子树被称作“左子树”和“右子树”。
每次写递归,都按照这三要素来写,可以保证大家写出正确的递归算法!
-
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
-
确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
-
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
# 前序遍历-递归-LC144_二叉树的前序遍历
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
# 保存结果
result = []
def traversal(root: TreeNode):
if root == None:
return
result.append(root.val) # 前序
traversal(root.left) # 左
traversal(root.right) # 右
traversal(root)
return result
# 中序遍历-递归-LC94_二叉树的中序遍历
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
result = []
def traversal(root: TreeNode):
if root == None:
return
traversal(root.left) # 左
result.append(root.val) # 中序
traversal(root.right) # 右
traversal(root)
return result
# 后序遍历-递归-LC145_二叉树的后序遍历
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
result = []
def traversal(root: TreeNode):
if root == None:
return
traversal(root.left) # 左
traversal(root.right) # 右
result.append(root.val) # 后序
traversal(root)
return result
# 前序遍历-迭代-LC144_二叉树的前序遍历
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
# 根结点为空则返回空列表
if not root:
return []
stack = [root]
result = []
while stack:
node = stack.pop()
# 中结点先处理
result.append(node.val)
# 右孩子先入栈
if node.right:
stack.append(node.right)
# 左孩子后入栈
if node.left:
stack.append(node.left)
return result
# 中序遍历-迭代-LC94_二叉树的中序遍历
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
if not root:
return []
stack = [] # 不能提前将root结点加入stack中
result = []
cur = root
while cur or stack:
# 先迭代访问最底层的左子树结点
if cur:
stack.append(cur)
cur = cur.left
# 到达最左结点后处理栈顶结点
else:
cur = stack.pop()
result.append(cur.val)
# 取栈顶元素右结点
cur = cur.right
return result
# 后序遍历-迭代-LC145_二叉树的后序遍历
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
if not root:
return []
stack = [root]
result = []
while stack:
node = stack.pop()
# 中结点先处理
result.append(node.val)
# 左孩子先入栈
if node.left:
stack.append(node.left)
# 右孩子后入栈
if node.right:
stack.append(node.right)
# 将最终的数组翻转
return result[::-1]
二、题目
1.简
144. 二叉树的前序遍历
给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
示例 1:
输入:root = [1,null,2,3]
输出:[1,2,3]
class Solution:
def preorderTraversal(self, root):
# 递归形式获取。前序遍历 中左右
result=[]
if root==None:
return []
def pre_tree(root,result):
if root==None:
return
result.append(root.val)
pre_tree(root.left,result)
pre_tree(root.right,result)
pre_tree(root,result)
return result
class Solution:
def preorderTraversal(self, root):
# 方法二:迭代,使用栈,先进后出
stack=[root]
if root==None:
return []
result=[]
while stack:
root=stack.pop()
result.append(root.val)
if root.right: # 栈的特点先进后出,所以先右节点,然后再左节点
stack.append(root.right)
if root.left:
stack.append(root.left)
return result
复杂度分析
- 时间复杂度:O(n),其中 n 是二叉树的节点数。每一个节点恰好被遍历一次。
- 空间复杂度:O(n),为递归过程中栈的开销,平均情况下为 O(logn),最坏情况下树呈现链状,为 O(n)。
94. 二叉树的中序遍历
给定一个二叉树的根节点 root ,返回它的 中序 遍历。
示例 1:
输入:root = [1,null,2,3]
输出:[1,3,2]
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
# 方法一:递归,中序遍历 左中右。
result=[]
if root==None:
return []
def median_tree(root,result):
if root is None:
return
median_tree(root.left,result)
result.append(root.val)
median_tree(root.right,result)
median_tree(root,result)
return result
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
"""
方法2:栈实现。
左中右
循环相对来说比递归要复杂的多,因为要在一个函数遍历所有的节点,
对于树的遍历基本上要使用栈这个结构
"""
stack=[]
sol=[]
curr=root
while curr or stack:
if curr:
stack.append(curr)
curr=curr.left
else:
curr=stack.pop()
sol.append(curr.val)
curr=curr.right
return sol
145. 二叉树的后序遍历
给定一个二叉树,返回它的 后序 遍历。
示例:
输入: [1,null,2,3]
1
2
/
3
输出: [3,2,1]
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
# 方法1:递归形式
def last_tree(root,result):
if root is None:
return
last_tree(root.left,result)
last_tree(root.right,result)
result.append(root.val)
result=[]
last_tree(root,result)
return result
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
# 方法2:迭代,还是通过栈实现,前序 中左右,后序左右中, 利用中左右 即中右左
if root==None:
return []
stack=[root]
result=[]
while stack:
curr=stack.pop()
result.append(curr.val)
if curr.left:
stack.append(curr.left)
if curr.right:
stack.append(curr.right)
return result[::-1]
复杂度分析
- 时间复杂度:O(n),其中 n 是二叉树的节点数。每一个节点恰好被遍历一次。
- 空间复杂度:O(n),为递归过程中栈的开销,平均情况下为 O(logn),最坏情况下树呈现链状,为 O(n)。
剑指 Offer 68 - II. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
# 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:
# 求某个满足要求的路径
def pre_tree(root,path,target):
if not root:
return False
path.append(root)
if root==target:
return True
l=pre_tree(root.left,path,target)
r=pre_tree(root.right,path,target)
if not l and not r:
path.pop()
return False
return True
stack=[]
stack1=[]
pre_tree(root,stack,p)
pre_tree(root,stack1,q)
index=0
while stack[index]==stack1[index] :
index+=1
if index==len(stack):
return stack[-1]
if index==len(stack1):
return stack1[-1]
return stack[index-1]
938. 二叉搜索树的范围和
给定二叉搜索树的根结点 root,返回值位于范围 [low, high] 之间的所有结点的值的和。
示例 1:
输入:root = [10,5,15,3,7,null,18], low = 7, high = 15
输出:32
# 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 rangeSumBST(self, root: TreeNode, low: int, high: int) -> int:
# 方法1:递归方式,前序遍历
self.sum_node=0
def forward_tree(root):
if root==None:
return
if low<=root.val and root.val<=high:
self.sum_node+=root.val
if root.left:
forward_tree(root.left)
if root.right:
forward_tree(root.right)
forward_tree(root)
return self.sum_node
class Solution:
def rangeSumBST(self, root: TreeNode, low: int, high: int) -> int:
# 方法2:迭代方式,的前序遍历
self.sum_node=0
stack=[root]
while stack:
curr=stack.pop()
if low<=curr.val<=high:
self.sum_node+=curr.val
if curr.right:
stack.append(curr.right)
if curr.left:
stack.append(curr.left)
return self.sum_node
复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),所有的点都要遍历。
- 空间复杂度: O ( n ) O(n) O(n),为递归过程中栈的开销,
700. 二叉搜索树中的搜索
给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。
例如,
给定二叉搜索树:
4
/ \
2 7
/ \
1 3
和值: 2
class Solution:
def searchBST(self, root: TreeNode, val: int) -> TreeNode:
# 思路1:和前序一样,直接递归遍历,就行了,由于是二叉搜索树,所以,可以做个小优化
self.result=None
def pre_tree(root):
if root is None:
return
if root.val==val:
self.result=root
return
if root.val<val:
pre_tree(root.right)
if root.val>val:
pre_tree(root.left)
pre_tree(root)
return self.result
复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),所有的点都要遍历。
- 空间复杂度: O ( n ) O(n) O(n),为递归过程中栈的开销,
class Solution:
def searchBST(self, root: TreeNode, val: int) -> TreeNode:
# 前序迭代 搜索二叉树 特点,左子树一定小于<跟节点<右子树
next_queue=[root]
while next_queue:
curr=next_queue.pop()
if curr:
if curr.val>val:
next_queue.append(curr.left)
elif curr.val<val:
next_queue.append(curr.right)
else:
return curr
if next_queue is None:
return False
复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),所有的点都要遍历。
- 空间复杂度: O ( n ) O(n) O(n),为递归过程中栈的开销,
404. 左叶子之和
计算给定二叉树的所有左叶子之和。
示例:
3
/
9 20
/
15 7
在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24
class Solution:
def sumOfLeftLeaves(self, root: TreeNode) -> int:
# 前序遍历
self.left_sum=0
def pre_tree(root):
if root is None:
return
if root.left: # 左叶子之和
if root.left.left is None and root.left.right is None:
self.left_sum+=root.left.val
pre_tree(root.left)
pre_tree(root.right)
pre_tree(root)
return self.left_sum
复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),所有的点都要遍历。
- 空间复杂度: O ( n ) O(n) O(n),为递归过程中栈的开销,
226. 翻转二叉树
翻转一棵二叉树。
示例:
输入:
4
/
2 7
/ \ /
1 3 6 9
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
# 前序遍历
def pre_tree(root):
if root is None:
return
root.left,root.right=root.right,root.left
pre_tree(root.left)
pre_tree(root.right)
pre_tree(root)
return root
复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),所有的点都要遍历。
- 空间复杂度: O ( n ) O(n) O(n),为递归过程中栈的开销,
235. 二叉搜索树的最近公共祖先
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
示例 1:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
# 思路1:进行遍历,以p,q为节点,寻找其路径。 路径对比,最后一个相同的点则为公共祖先
def pre_tree(root,target):
path=[]
node=root
while node!=target:
path.append(node)
if target.val<node.val:
node=node.left
else:
node=node.right
path.append(node)
return path
path_p=pre_tree(root,p)
path_q=pre_tree(root,q)
ancestor=None
for u,v in zip(path_p,path_q):
if u==v:
ancestor=u
else:
break
return ancestor
复杂度分析
-
时间复杂度:O(n),其中 n 是给定的二叉搜索树中的节点个数。上述代码需要的时间与节点 p 和 q 在树中的深度线性相关,而在最坏的情况下,树呈现链式结构,p 和 q 一个是树的唯一叶子结点,一个是该叶子结点的父节点,此时时间复杂度为 Θ(n)。
-
空间复杂度:O(n),我们需要存储根节点到 p 和 q 的路径。和上面的分析方法相同,在最坏的情况下,路径的长度为 Θ(n),因此需要 Θ(n) 的空间。
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
# 思路2:同时遍历,当有一个点的值介于p,q之间,则为返回的点
ancestor = root
while True:
if p.val < ancestor.val and q.val < ancestor.val:
ancestor = ancestor.left
elif p.val > ancestor.val and q.val > ancestor.val:
ancestor = ancestor.right
else:
break
return ancestor
复杂度分析
- 时间复杂度:O(n),其中 nn 是给定的二叉搜索树中的节点个数。分析思路与方法一相同。
- 空间复杂度:O(1)。
257. 二叉树的所有路径
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [1,2,3,null,5]
输出:[“1->2->5”,“1->3”]
class Solution:
def binaryTreePaths(self, root: TreeNode) -> List[str]:
# 前序遍历
self.path=[]
def pre_tree(root,path):
if root :
path+=str(root.val)
if not root.left and not root.right:
self.path.append(path)
path=''
return
else:
path+='->'
pre_tree(root.left,path)
pre_tree(root.right,path)
pre_tree(root,'')
return self.path
复杂度分析
- 时间复杂度: O ( n 2 ) O(n^2) O(n2),所有的点都要遍历。
- 空间复杂度: O ( n 2 ) O(n^2) O(n2),为递归过程中栈的开销,
513. 找树左下角的值
给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。
示例 1:
输入: root = [2,1,3]
输出: 1
# 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 findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
# 层次遍历解决,时间复杂度 o(n) 空间复杂度o(n)
# queue=deque([root])
# val=None
# while queue:
# node=queue.popleft()
# if node.right:
# queue.append(node.right)
# if node.left:
# queue.append(node.left)
# val=node.val
# return val
# 时间复杂度 o(n) 空间复杂度o(n)
curVal = curHeight = 0
def dfs(node,height):
if not node:
return
height+=1
dfs(node.left,height)
dfs(node.right,height)
nonlocal curHeight,curVal
if curHeight<height:
curHeight=height
curVal=node.val
dfs(root,0)
return curVal
104. 二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/
9 20
/
15 7
class Solution:
def maxDepth(self, root: TreeNode) -> int:
# 前序递归 解决长度问题
self.depth=0
def pre_tree(root,this_depth):
if root is None:
return
this_depth+=1
if root.left is None and root.right is None :
self.depth=max(self.depth,this_depth)
pre_tree(root.left,this_depth)
pre_tree(root.right,this_depth)
pre_tree(root,0)
return self.depth
复杂度分析
- 时间复杂度:O(n),其中 n 为二叉树节点的个数。每个节点在递归中只被遍历一次。
- 空间复杂度:O(height),其中 height 表示二叉树的高度。递归函数需要栈空间,而栈空间取决于递归的深度,因此空间复杂度等价于二叉树的高度。
class Solution:
def maxDepth(self, root: TreeNode) -> int:
# 思路2:深度优先搜索。
if root is None:
return 0
else:
left_depth=self.maxDepth(root.left)
right_depth=self.maxDepth(root.right)
return 1+max(left_depth,right_depth)
复杂度分析
- 时间复杂度:O(n),其中 n 为二叉树节点的个数。每个节点在递归中只被遍历一次。
- 空间复杂度:O(height),其中 height 表示二叉树的高度。递归函数需要栈空间,而栈空间取决于递归的深度,因此空间复杂度等价于二叉树的高度。
687. 最长同值路径
给定一个二叉树的 root ,返回 最长的路径的长度 ,这个路径中的 每个节点具有相同值 。 这条路径可以经过也可以不经过根节点。
两个节点之间的路径长度 由它们之间的边数表示。
输入:root = [5,4,5,1,1,5]
输出:2
# 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 longestUnivaluePath(self, root: Optional[TreeNode]) -> int:
# 思路,后序遍历,利用父节点的值与子节点进行比较,如果相等,则当前的长度+1,如果不相等则为0,返回最长子节点的长度,时间复杂度为o(n) 空间也为O(n)
self.ans=0
def tree(root):
if not root:
return 0
l=tree(root.left)
r=tree(root.right)
if root.left and root.left.val==root.val:
l+=1
else:
l=0
if root.right and root.right.val==root.val:
r+=1
else:
r=0
self.ans=max(self.ans,l+r)
return max(l,r)
tree(root)
return self.ans
515. 在每个树行中找最大值
给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。
示例1:
输入: root = [1,3,2,5,3,null,9]
输出: [1,3,9]
示例2:
输入: root = [1,2,3]
输出: [1,3]
# 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 largestValues(self, root: Optional[TreeNode]) -> List[int]:
# 层次遍历 时间空间都为 0(n)
# if not root:
# return []
# queue=deque([root])
# res=[]
# while queue:
# len_queue=len(queue)
# max_num=float(-inf)
# for i in range(len_queue):
# node=queue.popleft()
# max_num=max(max_num,node.val)
# if node.left:
# queue.append(node.left)
# if node.right:
# queue.append(node.right)
# res.append(max_num)
# return res
# 递归 存储每层的节点值,然后遍历 时间o(n) 空间 数的高度 0(hegit) ans 只存储每层最大值
ans=[]
def dfs(root,curheight):
if not root:
return
if curheight==len(ans):
ans.append(root.val)
else:
ans[curheight]=max(ans[curheight],root.val)
dfs(root.left,curheight+1)
dfs(root.right,curheight+1)
dfs(root,0)
return ans
589. N 叉树的前序遍历
给定一个 n 叉树的根节点 root ,返回 其节点值的 前序遍历 。
n 叉树 在输入中按层序遍历进行序列化表示,每组子节点由空值 null 分隔(请参见示例)。
示例 1:
输入:root = [1,null,3,2,4,null,5,6]
输出:[1,3,5,6,2,4]
"""
# Definition for a Node.
class Node:
def __init__(self, val=None, children=None):
self.val = val
self.children = children
"""
class Solution:
def preorder(self, root: 'Node') -> List[int]:
# 递归法 时间复杂度:0(n) 每个点需要遍历一次, 空间复杂度O(n) 递归需要栈 平均是o(logn),但是栈最深是o(n),因而复杂度是o(n)
# def pre_tree(root,res):
# if not root:
# return
# res.append(root.val)
# for chil in root.children:
# pre_tree(chil,res)
# res=[]
# pre_tree(root,res)
# return res
# 思路2:迭代法 ,利用栈实现 先进后出 时间复杂度和空间复杂度和递归一样
if not root:
return []
res=[]
stack=[root]
while stack:
node=stack.pop()
res.append(node.val)
for i in range(len(node.children)-1,-1,-1):
# 栈的特点先进后出,所以先右节点,然后再左节点
if node.children[i]:
stack.append(node.children[i])
return res
2.中
102. 二叉树的层序遍历
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
示例:
二叉树:[3,9,20,null,null,15,7],
3
/
9 20
/
15 7
返回其层序遍历结果:
[
[3],
[9,20],
[15,7]
]
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
# 方法1:迭代,单端队列,先进先出
if root==None:
return []
sol=[[]]
curr_queue=[root]
next_queue=[]
while curr_queue or next_queue:
if curr_queue:
curr=curr_queue.pop(0)
sol[-1].append(curr.val)
if curr.left:
next_queue.append(curr.left)
if curr.right:
next_queue.append(curr.right)
else:
curr_queue=next_queue
next_queue=[]
sol.append([])
return sol
复杂度分析
- 时间复杂度:O(n),其中 n 是二叉树的节点数。每一个节点恰好被遍历一次。
- 空间复杂度:O(n),最大o(n)。
# 递归法
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
res = []
def helper(root, depth):
if not root: return []
if len(res) == depth: res.append([]) # start the current depth
res[depth].append(root.val) # fulfil the current depth
if root.left: helper(root.left, depth + 1) # process child nodes for the next depth
if root.right: helper(root.right, depth + 1)
helper(root, 0)
return res