剑指offer-二叉树(python)
二叉树部分
本博客对剑指offer中的二叉树类型的题目进行总结和整理,并分析各类题目中容易出错的点和容易遗忘的边界条件等。
0. 构建二叉树
在练习剑指offer题目之前,先学习一下如何基于一个list构建二叉树。
思路:递归。
公式:节点i的左节点为(2*i+1),右节点为(2i+2)。(i从0开始)
# -*- coding:utf-8 -*-
# '二叉树结点类'
# '二叉树结点类'
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
# '列表创建二叉树'
def listcreattree(root,llist,i):###用列表递归创建二叉树,
#它其实创建过程也是从根开始a开始,创左子树b,再创b的左子树,如果b的左子树为空,返回none。
#再接着创建b的右子树,
if i < len(llist):
if llist[i] == '#':
return None
else:
root = TreeNode(llist[i])
print('列表序号 {}, the value is {}'.format( i, llist[i]))
root.left = listcreattree(root.left, llist, 2*i+1)
root.right = listcreattree(root.right, llist, 2*i+2)
print('return the root: {}'.format(root.val))
return root
return root
llist=['1','2','3','#','4','5']
listcreattree(None,llist,0)
1. 重建二叉树
题目描述:
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
示例:
输入:[1,2,3,4,5,6,7],[3,2,4,1,6,5,7]
返回值:{1,2,5,3,4,6,7}
思路:递归。1. 根据前序遍历的第一个结点确定根结点 2. 根据根结点在中序遍历中找到左右子树及其长度,并将前序和中序遍历中的左右子树分开 3. 对左右子树的前序和中序遍历进行相同的操作,进行递归。
# -*- coding:utf-8 -*-
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
# 返回构造的TreeNode根节点
def reConstructBinaryTree(self, pre, tin):
# 首先处理特殊情况,即树为空或只有一个结点
if len(pre) == 0:
return
if len(pre) == 1:
return TreeNode(pre[0])
else:
root = TreeNode(pre[0])
# 对左子树递归
root.left = self.reConstructBinaryTree(pre[1:tin.index(pre[0])+1], tin[0:tin.index(pre[0])])
# 对右子树递归
root.right = self.reConstructBinaryTree(pre[tin.index(pre[0])+1:], tin[tin.index(pre[0])+1:])
return root
2. 树的子结构
题目描述:
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
示例:
输入:{8,8,#,9,#,2,#,5},{8,9,#,2}
返回值:true
思路:这里存在两重递归。 1. 首先在A树的结点中递归地寻找和B树根结点相同的结点 2. 找到根结点以后,递归地判断该A树中结点的左右子树是否和B树中的结点一一对应。如果不对应,则继续递归地寻找下一个相同的根结点。
class Solution:
def HasSubtree(self, pRoot1, pRoot2):
# write code here
if pRoot1 is None or pRoot2 is None:
return False
result = False
if pRoot1.val == pRoot2.val:
result = self.isSubtree(pRoot1, pRoot2)
if result == False:
result = self.HasSubtree(pRoot1.left, pRoot2) | self.HasSubtree(pRoot1.right, pRoot2)
return result
def isSubtree(self, root1, root2):
if root2 is None:
return True
if root1 is None:
return False
if root1.val == root2.val:
return self.isSubtree(root1.left, root2.left) & self.isSubtree(root1.right, root2.right)
return False
3. 二叉树的镜像
题目描述:操作给定的二叉树,将其变换为源二叉树的镜像。
示例:
输入:{8,6,10,5,7,9,11}
返回值:{8,10,6,11,9,7,5}
思路:新建一个相同的根结点,然后进行递归:该结点的左孩子等于原结点的右孩子,该结点的右孩子等于原结点的左孩子。
class Solution:
def Mirror(self, pRoot):
# write code here
if pRoot is None:
return None
root = TreeNode(pRoot.val)
root.left = self.Mirror(pRoot.right)
root.right = self.Mirror(pRoot.left)
return root
4. 从上往下打印二叉树
题目描述:从上往下打印出二叉树的每个节点,同层节点从左至右打印。
示例:
输入:{5,4,#,3,#,2,#,1}
返回值:[5,4,3,2,1]
思路:树+队列。循环的方法。
- 新建两个空队列(list)作为结点队列和值队列。首先将根结点压入结点队列
- 当结点队列非空时,进行循环:弹出队列的队头结点, 将该结点的值存入值队列。将该结点的左右子结点依此压入结点队列。直到结点队列为空,所有的结点的值均被存入值队列。
class Solution:
# 返回从上到下每个节点值列表,例:[1,2,3]
def PrintFromTopToBottom(self, root):
# write code here
queue = []
result = []
if root is None:
return result
queue.append(root)
while queue:
cur_Node = queue.pop(0)
result.append(cur_Node.val)
if cur_Node.left is not None:
queue.append(cur_Node.left)
if cur_Node.right is not None:
queue.append(cur_Node.right)
return result
5. 二叉搜索树的后序遍历序列
题目描述:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。(ps:我们约定空树不是二叉搜素树)
示例:
输入:[4,8,6,12,16,14,10]
返回值:true
思路:
二叉搜索树的特点:左子树 < 根结点 < 右子树
后序遍历的特点:元素先后顺序是[ [左子树元素] [右子树元素] [根结点] ],即根结点在最后。
编程逻辑:
- 取数组最后一个元素作为根结点的值。
- 从左往后找出小于根结点的元素及其索引i。然后判断i之后的元素(不包括根结点)是否小于根结点,不满足则返回False。
- 重复上述步骤,进行递归。若左子树元素存在,递归左子树;若右子树元素存在,递归右子树。当左子树和右子树都返回True时,最终结果为True.
# -*- coding:utf-8 -*-
class Solution:
def VerifySquenceOfBST(self, sequence):
# write code here
if not sequence:
return False
root = sequence[-1]
# 找出左子树和右子树的分界线
for i in range(len(sequence)):
if sequence[i] > root:
break
# 验证右子树是否满足后序遍历
for j in range(i, len(sequence)-1):
if sequence[j] < root:
return False
# 开始递归,重复上述步骤,先递归左子树,再递归右子树
left = True
if i >0: #i > 0 的时候证明有左孩子,i<0就证明没有。说明左树已经遍历完毕
left = self.VerifySquenceOfBST(sequence[:i])
right = True
# 通过i的值不在最后一个结点判断是否有右孩子,len(sequence) - 1 为sequence的最后一个结点.如果i已经在最后一个节点了,则不进行判断
if i < len(sequence)-1:
right = self.VerifySquenceOfBST(sequence[i:-1])
return left and right
6. 二叉树中和为某一值的路径
问题描述:输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
示例1
输入:{10,5,12,4,7},22
返回值:[[10,5,7],[10,12]]
示例2
输入:{10,5,12,4,7},15
返回值:[ ]
思路一:
- 深度优先搜索,找出所有的路径。(注意,当root.left和root.right同时为None时,该结点为叶子节点,此时保存路径。)
- 然后遍历地对每一条路径求和,找出所有符合条件的。
import copy
class Solution:
# 返回二维列表,内部每个列表表示找到的路径
def FindPath(self, root, expectNumber):
# write code here
if not root:
return []
path = []
result = []
expect = []
result = self.collectPath(root, path, result)
for i in range(len(result)):
if sum(result[i]) == expectNumber:
expect.append(result[i])
return expect
def collectPath(self, root, path, result):
if not root:
return
path.append(root.val)
self.collectPath(root.left, path, result)
self.collectPath(root.right, path, result)
# 当到达叶子节点时,保存完整路径
if not root.left and not root.right:
result.append(copy.deepcopy(path))
# 弹出叶子节点,回到上一级节点
path.pop()
return result
思路二:更加简洁!!推荐!
在进行深度优先搜索(dfs)时,就对路径之和进行筛选,符合条件的保存。
class Solution:
# 返回二维列表,内部每个列表表示找到的路径
def FindPath(self, root, expectNumber):
# write code here
self.expectNumber = expectNumber
res = []
if not root:
return res # 注意,当树为空时,返回[],而不是None
self.dfs(root, res, [root.val])
return res
def dfs(self, root, res, path):
if not root.left and not root.right and sum(path) == self.expectNumber:
res.append(path)
if root.left:
# 注意这里的 path+[root.left.val] 的写法,省去了思路一中的path.pop()操作
self.dfs(root.left, res, path+[root.left.val])
if root.right:
self.dfs(root.right, res, path+[root.right.val])
7. 二叉树的深度
题目描述:输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
示例1
输入:{1,2,3,4,5,#,6,#,#,7}
返回值:4
思路:递归。(递归的终点都是回到条件1或2)
- 若根结点为空,深度为0.
- 若根结点无左右结点,则深度为1.
- 若根结点只有左结点(或只有右结点),深度=1+ 左子树深度(或右子树深度)
- 若根结点既有左结点,又有右结点,深度=1+ max(左子树深度,右子树深度)
2,3都可以视为4的特例,故可以合并到4。
class Solution:
def TreeDepth(self, pRoot):
# write code here
if pRoot is None:
return 0
else:
return 1 + max(self.TreeDepth(pRoot.left), self.TreeDepth(pRoot.right))
8. 平衡二叉树
题目描述:输入一棵二叉树,判断该二叉树是否是平衡二叉树。
在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树
平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
示例
输入:{1,2,3,4,5,6,7}
返回值:true
思路:先判断根结点处,左右子树的高度差是否满足条件;然后依此递归左右子树是否满足平衡二叉树条件。
class Solution:
def IsBalanced_Solution(self, pRoot):
# write code here
if not pRoot:
return True
if abs(self.depth(pRoot.left) - self.depth(pRoot.right)) > 1:
return False
if pRoot.left:
self.IsBalanced_Solution(pRoot.left)
if pRoot.right:
self.IsBalanced_Solution(pRoot.right)
return True
def depth(self, root):
if not root:
return 0
else:
return 1 + max(self.depth(root.left), self.depth(root.right))
9. 二叉树的下一个结点
题目描述:给定一个二叉树其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的next指针。
思路:中序遍历中结点的下一个结点有三种情况,
1)二叉树为空,返回None
2)当该结点的右孩子存在时,则从该结点的右孩子出发对右子树进行中序遍历找到叶子节点为止;
3)当该结点的右孩子不存在时,则往该结点的父结点回溯。
首先判断是否为根结点,若是则返回None。若不是,则继续判断该结点是否为其父结点的左孩子,若为左孩子,则下一个结点为其父结点,若为右孩子,则继续往上遍历父结点的父结点,重复上述判断。
class Solution:
def GetNext(self, pNode):
# write code here
if pNode.right is None: # 无右孩子,则往父结点回溯
return self.getnext(pNode)
else: # 有右孩子,则对右孩子进行中序遍历,找到第一个结点
return self.getleft(pNode.right)
def getnext(self, Node): # search the parent node
if Node.next is not None: # 不是根结点
if Node.next.left != Node: # 该结点为父结点的右孩子,继续向上遍历,重复判断
return self.getnext(Node.next)
else: # 该结点为父结点的左孩子,下一个结点为父结点
return Node.next
else:
return None
def getleft(self, Node): # search the left leaf node of the right subtree
while Node.left is not None:
Node = Node.left
return Node
写法二:用循环来代替递归,更加简洁
class Solution:
def GetNext(self, pNode):
if not pNode: # 树为空树时
return None
if pNode.right: # 结点的右孩子存在
pNode = pNode.right
while pNode.left: # 一直向左遍历
pNode = pNode.left
return pNode
else: # 结点的右孩子不存在,向父结点遍历
while pNode.next:
if pNode == pNode.next.left: #判断该结点是否为父结点的左孩子,若是则返回父结点,否则继续往上遍历
return pNode.next
pNode = pNode.next
return None
10. 对称的二叉树
题目描述
请实现一个函数,用来判断一棵二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
示例1
输入: {8,6,6,5,7,7,5}
返回值:true
示例2
输入:{8,6,9,5,7,7,5}
返回值:false
思路:
class Solution:
def isSymmetrical(self, pRoot):
if pRoot is None:
return True
return self.isSame(pRoot.left, pRoot.right)
def isSame(self, left, right):
if not left and not right: # 若左孩子和右孩子均为空,则返回True
return True
if not left or not right: # 若左孩子和右孩子只有一个为空,则非对称
return False
if left.val == right.val: # 若左孩子结点=右孩子结点,则继续向下遍历
return self.isSame(left.left, right.right) and self.isSame(left.right, right.left)
else:
return False
11. 按之字形顺序打印二叉树
题目描述
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
示例1
输入:{8,6,10,5,7,9,11}
返回值:[[8],[10,6],[5,7,9,11]]
思路:首先进行层序遍历,然后打印时奇顺偶逆。
class Solution:
def Print(self, pRoot):
# write code here
if not pRoot:
return [] #注意,这里输出空list,而不是None
sta = []
lists = []
index = 0 # 记录层数的奇偶
sta.append(pRoot)
while sta:
res = []
length = len(sta)
for i in range(length):
Node = sta.pop(0)
res.append(Node.val)
if Node.left:
sta.append(Node.left)
if Node.right:
sta.append(Node.right)
if index % 2 == 0: # 偶数层顺序,奇数层逆序
lists.append(res)
else:
res.reverse()
lists.append(res)
index += 1
return lists # 当没有return时会报错:[object of type 'NoneType' has no len()]
12. 把二叉树打印成多行
题目描述:从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
示例1
输入:{8,6,10,5,7,9,11}
返回值:[[8],[6,10],[5,7,9,11]]
思路:和上一题类似,利用队列进行二叉树的层序遍历!
class Solution:
# 返回二维列表[[1,2],[4,5]]
def Print(self, pRoot):
# write code here
stack1 = []
stack2 = []
if not pRoot:
return stack2
stack1.append(pRoot)
while stack1:
layer = []
l = len(stack1)
for i in range(l):
Node = stack1.pop(0)
layer.append(Node.val)
if Node.left:
stack1.append(Node.left)
if Node.right:
stack1.append(Node.right)
stack2.append(layer)
return stack2
13. 序列化二叉树
题目描述:
请实现两个函数,分别用来序列化和反序列化二叉树。
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
例如,我们可以把一个只有根节点为1的二叉树序列化为"1,",然后通过自己的函数来解析回这个二叉树。
示例1
输入:{8,6,10,5,7,9,11}
返回值:{8,6,10,5,7,9,11}
思路:
序列化二叉树:将结点值逐个取出保存为字符串。我们这里选择先序遍历的方式进行序列化,value字符之间用 “,”隔开。需要注意的是,如果遇到空结点,则用“#”表示。
反序列化:首先将字符串切分成list, 然后按照序列化的遍历方式重构二叉树。
class Solution:
# 序列化,这里按照先序遍历存储
def Serialize(self, root):
# write code here
if root is None:
return '#'
return str(root.val) + ',' + self.Serialize(root.left) + ',' + self.Serialize(root.right)
# 反序列化,根据序列化所采用的遍历顺序,重构二叉树
def Deserialize(self, s):
# write code here
list0 = s.split(',') # 将字符串切分成list
return self.reContructTree(list0)
def reContructTree(self, list0):
value = list0.pop(0)
root = None # important! Don't forget!
if value != '#':
root = TreeNode(int(value))
root.left = self.reContructTree(list0)
root.right = self.reContructTree(list0)
return root
14. 二叉搜索树的第k个结点
题目描述:给定一棵二叉搜索树,请找出其中的第k小的TreeNode结点。
示例1
输入:{5,3,7,2,4,6,8},3
返回值:{4}
说明:按结点数值大小顺序第三小结点的值为4
思路一: 按照二叉搜索树的左、中、右顺序进行遍历。设定一个全局计数器,每遍历一个结点,计数器+1。设定计数器==k为递归的出口,符合条件时,递归结束。(没有多余的计算,推荐思路一。)
class Solution:
# 返回对应节点TreeNode
def __init__(self):
self.idx = 0
def KthNode(self, pRoot, k):
# write code here
if pRoot is None or k <= 0: # 若树为空或k<0,则返回None
return None
# 中序遍历。特殊的一点是,在中间输出结点时,判断这是第几个结点。
node = self.KthNode(pRoot.left, k) # 向左遍历,直到pRoot.left为空(pRoot为叶子节点或pRoot无左孩子)
if node: #向左遍历到尽头,返回结点,全局计数器self.idx += 1
return node
self.idx += 1
if self.idx == k: # 当全局计数器==k时,返回该结点(该位置为递归的出口,重要!)
return pRoot
node = self.KthNode(pRoot.right, k) # 向右遍历,重复上述步骤
return node
思路二:简单粗暴法。先来一遍中序遍历,在list中存储所有的值,然后打印第k个。(有多余的遍历!)
def KthNode(self, pRoot, k):
# write code here
if (pRoot is None) or (k <= 0):
return None
res = []
self.inorder(pRoot, res)
if k > len(res):
return None
return res[k-1]
def inorder(self, node, res): # 中序遍历
if node is None:
return
self.inorder(node.left, res)
res.append(node)
self.inorder(node.right, res)
暂时先整理到这里,想到什么再回来补充!
经验不足,如发现有遗漏或思路不对之处,欢迎大家留言指出。如果大神们愿意指教更简洁的解法,更加感激不尽!
备注:如若转载,请注明出处!