- 解题三部曲
- 二叉树的最大深度
- 深度优先(DFS)
- 广度优先(BFS)
- 二叉树的层序遍历">二叉树的层序遍历
- 二叉树的最小深度
- 二叉树的最大深度">二叉树的最大深度
- 验证二叉搜索树">验证二叉搜索树
- 二叉树镜像">二叉树镜像
- 对称二叉树">对称二叉树
- 二叉树中和为某一值的路径">二叉树中和为某一值的路径
- 二叉搜索树中的插入操作">二叉搜索树中的插入操作
- 删除二叉搜索树中的节点">删除二叉搜索树中的节点
- 树的子结构">树的子结构
- 按之字形顺序打印二叉树">按之字形顺序打印二叉树
- 二叉搜索树中第K小的元素">二叉搜索树中第K小的元素
- 二叉树的最近公共祖先">二叉树的最近公共祖先
- 寻找重复的子树">寻找重复的子树
- 二叉搜索树转换为双向链表*">二叉搜索树转换为双向链表*
- 从前序与中序遍历序列构造二叉树">从前序与中序遍历序列构造二叉树
何谓递归?程序反复调用自身即是递归。此处禁止套娃!
以下会举例说明我对递归的一点理解,如果你不想看下去了,请记住这几个问题怎么回答:
- 如何给一堆数字排序? 答:分成两半,先排左半边再排右半边,最后合并就行了,至于怎么排左边和右边,请重新阅读这句话。
- 孙悟空身上有多少根毛? 答:一根毛加剩下的毛。
- 你今年几岁? 答:去年的岁数加一岁,1999 年我出生。
递归代码最重要的两个特征:结束条件和自我调用。自我调用是在解决子问题,而结束条件定义了最简子问题的答案。
解题三部曲
- 找整个递归的终止条件:递归应该在什么时候结束?
- 找返回值:应该给上一级返回什么信息?
- 本级递归应该做什么:在这一级递归中,应该完成什么任务?
二叉树的最大深度
- 找终止条件。什么情况下递归结束?当然是树为空的时候,此时树的深度为0,递归就结束了。
- 找返回值。应该返回什么?题目求的是树的最大深度,我们需要从每一级得到的信息自然是当前这一级对应的树的最大深度,因此我们的返回值应该是当前树的最大深度,这一步可以结合第三步来看。
- 本级递归应该做什么。首先,还是强调要走出之前的思维误区,递归后我们眼里的树一定是这个样子的,看下图。此时就三个节点:root、left、right,其中根据第二步,left和right分别记录的是root的左右子树的最大深度。那么本级递归应该做什么就很明确了,自然就是在root的左右子树中选择较大的一个,再加上1就是以root为根的子树的最大深度了,然后再返回这个深度即可。
深度优先(DFS)
对于一颗二叉树,深度优先搜索(Depth First Search)是沿着树的深度遍历树的节点,尽可能深的搜索树的分支。
先序遍历(根左右)
class Solution(object):
def preorderTraversal(self, root):
方法一:递归
res = []
def dfs(root):
if root == None:
return
res.append(root.val)
dfs(root.left)
dfs(root.right)
dfs(root)
return res
方法二:循环+栈迭代实现
if not root:
return
stack = []
node = root
res = []
while stack or node:
while node:
# 先保存当前值
res.append(node.val)
# 把左边所有都压入栈
stack.append(node)
node = node.left
# 然后再把右边所有都压入栈
node = stack.pop()
node = node.right
return res
中序遍历(左根右)
class Solution(object):
def inorderTraversal(self, root):
方法一:递归
res = []
def dfs(root):
if not root:
return
dfs(root.left)
res.append(root.val)
dfs(root.right)
dfs(root)
return res
方法二:循环+栈迭代实现
if not root:
return
stack = []
node = root
res = []
while stack or node:
# 先把左边所有都压入栈
while node:
stack.append(node)
node = node.left
# 将栈里的节点的值保存到res
node = stack.pop()
res.append(node.val)
# 然后再把右边所有都压入栈
node = node.right
return res
后序遍历(左右根)
class Solution(object):
def postorderTraversal(self, root):
方法一:递归
res = []
def dfs(root):
if not root:
return
dfs(root.left)
dfs(root.right)
res.append(root.val)
dfs(root)
return res
方法二:循环+栈迭代
if not root:
return
stack = []
node = root
res = []
while stack or node:
while node: # 下行循环,直到找到第一个叶子节点
stack.append(node)
if node.left: # 能左就左,不能左就右
node = node.left
else:
node = node.right
node = stack.pop()
res.append(node.val)
# 如果当前节点是上一节点的左子节点,则遍历右子节点
if stack and node == stack[-1].left:
node = stack[-1].right
else:
node = None
return res
方法三:投机取巧法,使用逆先序遍历
if not root:
return []
stack = []
node = root
res = []
while stack or node:
while node:
res.append(node.val)
stack.append(node)
node = node.right # 此处和下面的left相反
node = stack.pop()
node = node.left
return res[::-1]
广度优先(BFS)
从树的root开始,从上到下从从左到右遍历整个树的节点
class Solution:
def levelOrder(self, root: TreeNode) -> List[int]:
# 利用队列实现树的层次遍历
if not root:
return
res = []
queue = [root]
while queue:
node = queue.pop(0)
res.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return res
二叉树的层序遍历
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)
示例:二叉树:[3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层序遍历结果:
[
[3],
[9,20],
[15,7]
]
class Solution(object):
def levelOrder(self, root):
# 利用队列实现树的层次遍历
if not root:
return []
res = []
queue = [root]
while queue:
# 因为题目要求用子列表表示,这里是和广度优先遍历唯一区别
level = []
for _ in range(len(queue)):
node = queue.pop(0)
level.append(node.val)
# 如果左子节点存在,入队列
if node.left:
queue.append(node.left)
# 如果右子节点存在,入队列
if node.right:
queue.append(node.right)
res.append(level) # 如果是从下向上遍历,这需要把这里改成头插 res.insert(0, level)
return res
二叉树的最小深度
class Solution:
# 方法一:递归
# 可以通过递归求左右节点的最小深度的较小值,也可以层序遍历找到第一个叶子节点所在的层数。
def minDepth(self, root: TreeNode) -> int:
if not root:
return
if not root.left and not root.right:
return 1
if not root.right:
return 1 + self.minDepth(root.left)
if not root.left:
return 1 + self.minDepth(root.right)
return 1 + min(self.minDepth(root.left), self.minDepth(root.right))
# 方法二:BFS利用队列实现树的层次遍历
if not root:
return 0
depth = 1
queue = [root]
while queue:
for i in range(len(queue)):
node = queue.pop(0) # 切记要先取出
if not node.left and not node.right:
return depth
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
depth += 1
return depth
二叉树的最大深度
基本思路就是递归,当前树的最大深度等于(1+max(左子树最大深度,右子树最大深度))
class Solution:
def maxDepth(self, root):
# 方法一:dfs递归
if not root:
return 0
return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right))
# 方法二:BFS利用队列实现树的层次遍历
if not root:
return 0
queue = [root]
depth = 0
while queue:
for i in range(len(queue)):
node = queue.pop(0)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
depth += 1
return depth
验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。根节点的值为 5 ,但是其右子节点值为 4 。
class Solution(object):
def isValidBST(self, root):
# 方法一:中序遍历
stack = []
inorder = float('-inf')
while stack or root:
while root:
stack.append(root)
root = root.left
root = stack.pop()
# 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
if root.val <= inorder:
return False
inorder = root.val
root = root.right
return True
# 方法二:递归,根据左<中<右的特点判断
def dfs(root, left, right):
if not root:
return True
elif left < root.val < right:
return dfs(root.left, left, root.val) and dfs(root.right, root.val, right)
else:
return False
return dfs(root,float('-inf'), float('inf'))
二叉树镜像
class Solution:
def Mirror(self, root):
if not root:
return
#互换左右孩子的值
root.right, root.left = root.left, root.right
#递归处理左子树
self.Mirror(root.right)
#递归处理右子树
self.Mirror(root.left)
return root
对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/ \
2 2
\ \
3 3
class Solution(object):
def isSymmetric(self, root):
# 方法一:递归算法
if not root:
return True
def dfs(left, right):
# 递归的终止条件是两个节点都为空
# 或者两个节点中有一个为空
# 或者两个节点的值不相等
if not left and not right:
return True
if not left or not right:
return False
if left.val != right.val:
return False
ret1 = dfs(left.left, right.right)
ret2 = dfs(left.right, right.left)
return ret1 and ret2
# 用递归函数,比较左节点,右节点
return dfs(root.left, root.right)
# 方法二:迭代算法
if not root:
return True
deq = deque([root.left, root.right])
while deq:
root_right = deq.pop()
root_left = deq.pop()
if root_left == None and root_right == None:
continue
if root_left == None or root_right == None:
return False
if root_left.val != root_right.val:
return False
deq.extend([root_left.left, root_right.right, root_left.right, root_right.left])
return True
二叉树中和为某一值的路径
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
示例:
给定如下二叉树,以及目标和 sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
返回:
[
[5,4,11,2],
[5,8,4,5]
]
class Solution(object):
def pathSum(self, root, target):
# 栈+递归方法
lists = []
stack = []
def dfs(root, nums):
if not root:
return
stack.append(root.val)
nums -= root.val
if nums == 0 and not root.left and not root.right:
lists.append(list(stack))
dfs(root.left, nums)
dfs(root.right, nums)
stack.pop()
dfs(root, target)
return lists
二叉搜索树中的插入操作
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
输入:root = [4,2,7,1,3], val = 5
输出:[4,2,7,1,3,5]
class Solution(object):
def insertIntoBST(self, root, val):
if not root:
return TreeNode(val)
node = root
# 思路不难,比较node.val和val,小并且左边没孩子就放左边,大并且右边没孩子就放右边
while node:
if val < node.val:
if not node.left:
node.left = TreeNode(val)
break
else:
node = node.left
else:
if not node.right:
node.right = TreeNode(val)
break
else:
node = node.right
return root
删除二叉搜索树中的节点
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
root = [5,3,6,2,4,null,7]
key = 3
5
/ \
3 6
/ \ \
2 4 7
给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7]
# 找到后有四种情况
# case0 结点不存在,empty,return root
# case1 结点没孩子,直接删除
# case2 结点只有一个孩子,是左孩子,让左孩子即位
# case3 结点只有一个孩子,是右孩子,让右孩子即位
# case4 结点有俩孩子,用 right tree left most 替换待删除结点,然后把right tree left most那个结点删除
class Solution:
def deleteNode(self, root, key):
# 方法一:慢慢找一层一层遍历
if root is None:
return root
if root.val > key:
root.left = self.deleteNode(root.left, key)
elif root.val < key:
root.right = self.deleteNode(root.right, key)
else:
# case1 没有孩子,直接删除返回空
if not root.left and not root.right:
root = None
return root
# case2 只有一个孩子,左孩子,让左孩子即位
elif root.left and not root.right:
tmp = root.left
root = None
return tmp
# case3 只有一个孩子,右孩子,让右孩子即位
elif not root.left and root.right:
tmp = root.right
root = None
return tmp
# case4 有两个孩子,和右子树 left most交换后在右子树中删除 left most
else:
curr = root.right # 右子树
while curr.left: # find left most
curr = curr.left
# 退出while循环时候的curr就是left most
root.val = curr.val # 即位
root.right = self.deleteNode(root.right, curr.val) # 在右子树中删除该结点后作为新的右子树
return root
# 方法二:递归
def dfs(root, key, l):
if not root:
return l
if root.val == key:
return dfs(root.right, key, root.left)
elif root.val < key:
root.right = dfs(root.right, key, l)
return root
else:
root.left = dfs(root.left, key, l)
return root
return dfs(root, key, None)
树的子结构
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
给定的树 A:
3
/ \
4 5
/ \
1 2
给定的树 B:
4
/
1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
输入:A = [1,2,3], B = [3,1]
输出:false
输入:A = [3,4,5,1,2], B = [4,1]
输出:true
class Solution(object):
def isSubStructure(self, A, B):
if not A or not B:
return False
def dfs(A, B):
if not B:
return True
if not A:
return False
if A.val != B.val:
return False
return dfs(A.left, B.left) and dfs(A.right, B.right)
return dfs(A, B) or self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B)
按之字形顺序打印二叉树
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
class Solution(object):
def zigzagLevelOrder(self, root):
# 方法一:按照广度优先的模版套就行了,区别就是正向插入和反向插入
if not root:
return []
# index记录层数奇偶
index = 1
res = []
queue = [root]
while queue:
# 获取queue的长度,因为队列的长度是在不断变化的,这里需先确定要遍历多少次
level = []
for _ in range(len(queue)):
node = queue.pop(0)
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
# 当index为奇数时,就正向输出
if index & 1 == 1:
res.append(level)
# 当index偶位数时,就反向输出,即先调用一次reverse,再保存
else:
res.append(level[::-1])
index += 1
return res
# 方法二:使用递归
if not root:
return []
# index记录层数奇偶
index = 0
res = []
def dfs(root, index):
if not root:
return
if len(res) == index:
res.append([])
# 当index为偶数时,就直接头插
if index & 1 == 0:
res[index].append(root.val)
# 当index为奇数时,就直接尾插
else:
res[index].insert(0, root.val)
dfs(root.left, index + 1)
dfs(root.right, index + 1)
dfs(root, index)
return res
二叉搜索树中第K小的元素
给定一棵二叉搜索树,请找出其中的第k小的结点。例如,(5,3,7,2,4,6,8)中,按结点数值大小顺序第三小结点的值为4。
class Solution:
def kthSmallest(self, root, k):
# 方法一:DFS中序遍历递归法,然后根据切片取值
res = []
def dfs(root):
if not root:
return
dfs(root.left) # 如果求第K大的元素,和下面的right互换即可
res.append(root.val)
dfs(root.right)
dfs(root)
return res[k-1]
# 方法二:DFS中序遍历循环压栈法
stack = []
node = root
while True:
# 先把左边所有都压入栈
while node:
stack.append(node)
node = root.left
node = stack.pop()
# 唯一区别就是这里计数了
k -= 1
if k == 0:
return node.val
node = node.right
二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
class Solution(object):
def lowestCommonAncestor(self, root, p, q):
if not root:
return None
# 边界条件,如果匹配到left或right就直接返回停止递归
if root.val == p.val or root.val == q.val:
return root
# 因为是DFS算法,这个模板可以无脑套用,写上之后可能你思路就清晰很多
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
# 如果左子树找不到,那就在右子树,如果右子树找不到,那就在左子树
if not left:
return right
if not right:
return left
return root
寻找重复的子树
给定一棵二叉树,返回所有重复的子树。对于同一类的重复子树,你只需要返回其中任意一棵的根结点即可。
两棵树重复是指它们具有相同的结构以及相同的结点值。
1
/ \
2 3
/ / \
4 2 4
/
4
下面是两个重复的子树:
2
/
4
和
4
class Solution(object):
def findDuplicateSubtrees(self, root):
# 递归
# “重复”也就是说既需要知道以当前节点为根的子树什么样,也需要知道以其他节点为根的子树长啥样
dicts = {}
res = []
def dfs(root):
if not root:
return '#'
tmp = dfs(root.left) + ',' + dfs(root.right) + ',' + str(root.val)
if tmp in dicts:
dicts[tmp] += 1
else:
dicts[tmp] = 1
if dicts[tmp] == 2:
res.append(root)
return tmp
dfs(root)
return res
二叉搜索树转换为双向链表*
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
为了让您更好地理解问题,以下面的二叉搜索树为例:
我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。
下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。
特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。
class Solution(object):
def treeToDoublyList(self, root):
# 1.这里定义全局的head和pre。
# 2.整体思路按照中序遍历来走,pre如果为空则表示是这棵树左下的节点,那么将其定义为头节点。
# 3.如果pre不空的话,就表示当前遍历的节点的左子树的右下节点。
# 4.遍历完成后,pre指向这棵树右下的节点。
# 5.最后将其首尾连接起来。
if not root:
return
def dfs(cur):
if not cur:
return
dfs(cur.left) # 递归左子树
if self.pre: # 修改节点引用
self.pre.right, cur.left = cur, self.pre
else: # 记录头节点
self.head = cur
self.pre = cur # 保存 cur
dfs(cur.right) # 递归右子树
self.pre = None
dfs(root)
self.head.left, self.pre.right = self.pre, self.head
return self.head
从前序与中序遍历序列构造二叉树
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:你可以假设树中没有重复的元素。
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
Solution(object):
def buildTree(self, preorder, inorder):
if len(inorder) == 0:
return None
# 创建一棵新树
root = TreeNode(preorder[0])
# 在中序中找到前序第一个节点的位置,中序遍历中mid的左边全是左子树,右边全是右子树
mid = inorder.index(preorder[0])
# 构建左子树
root.left = self.buildTree(preorder[1:mid+1], inorder[:mid])
# 构建右子树
root.right = self.buildTree(preorder[mid+1:], inorder[mid+1:])
return root