【面试必刷TOP101】系列包含:
- 面试必刷TOP101:链表(01-05,Python实现)
- 面试必刷TOP101:链表(06-10,Python实现)
- 面试必刷TOP101:链表(11-16,Python实现)
- 面试必刷TOP101:二分查找/排序(17-22,Python实现)
- 面试必刷TOP101:二叉树系列(23-30,Python实现)
- 面试必刷TOP101:二叉树系列(31-36,Python实现)
- 面试必刷TOP101:二叉树系列(37-41,Python实现)
- 面试必刷TOP101:堆、栈、队列(42-49,Python实现)
- 面试必刷TOP101:哈希表(50-54,Python实现)
- 面试必刷TOP101:递归 / 回溯(55-61,Python实现)
- 面试必刷TOP101:动态规划(入门)(62-66,Python实现)
- 面试必刷TOP101:动态规划(67-71,Python实现)
- 面试必刷TOP101:动态规划(72-77,Python实现)
- 面试必刷TOP101:动态规划(78-82,Python实现)
- 面试必刷TOP101:字符串(83-86,Python实现)
- 面试必刷TOP101:双指针(87-94,Python实现)
- 面试必刷TOP101:贪心算法(95-96,Python实现)
- 面试必刷TOP101:模拟(97-99,Python实现)
面试必刷TOP101:二叉树系列(23-30,Python实现)
23.二叉树的前序遍历
23.1 递归法
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
#
class Solution:
def preorder(self, list: List[int], root: TreeNode):
if root == None:
return None
list.append(root.val)
self.preorder(list, root.left)
self.preorder(list, root.right)
def preorderTraversal(self , root: TreeNode) -> List[int]:
list = []
self.preorder(list, root)
return list
时间复杂度:O(n),其中 n 为二叉树的节点数,遍历二叉树所有节点。
空间复杂度:O(n),最坏情况下二叉树化为链表,递归栈深度为 n。
23.2 借助栈
class Solution:
def preorderTraversal(self , root: TreeNode) -> List[int]:
if root == None:
return None
a, b = [], []
a.append(root)
while a:
res = a.pop()
b.append(res.val)
if res.right != None:
a.append(res.right)
if res.left != None:
a.append(res.left)
return b
时间复杂度:O(n),其中 n 为二叉树的节点数,遍历二叉树所有节点。
空间复杂度:O(n),辅助栈空间最大为链表所有节点数。
24.二叉树的中序遍历
24.1 递归法
import sys
class Solution:
def inorder(self, a: List[int], root: TreeNode):
if root == None:
return
self.inorder(a, root.left)
a.append(root.val)
self.inorder(a, root.right)
def inorderTraversal(self , root: TreeNode) -> List[int]:
sys.setrecursionlimit(1500)
a = []
self.inorder(a, root)
return a
时间复杂度:O(n),其中 n 为二叉树的节点数,遍历二叉树所有节点。
空间复杂度:O(n),最坏情况下二叉树化为链表,递归栈深度为 n。
24.2 借助栈
import sys
class Solution:
def inorderTraversal(self , root: TreeNode) -> List[int]:
a, b = [], []
if root == None:
return
while root or b:
while root:
b.append(root)
root = root.left
res = b.pop()
a.append(res.val)
root = res.right
return a
时间复杂度:O(n),其中 n 为二叉树的节点数,遍历二叉树所有节点。
空间复杂度:O(n),辅助栈空间最大为链表所有节点数。
25.二叉树的后序遍历
25.1 递归法
class Solution:
def postorder(self, a: List[int], root: TreeNode):
if root == None:
return None
self.postorder(a, root.left)
self.postorder(a, root.right)
a.append(root.val)
def postorderTraversal(self , root: TreeNode) -> List[int]:
a = []
self.postorder(a, root)
return a
时间复杂度:O(n),其中 n 为二叉树的节点数,遍历二叉树所有节点。
空间复杂度:O(n),最坏情况下二叉树化为链表,递归栈深度为 n。
25.2 借助栈
class Solution:
def postorderTraversal(self , root: TreeNode) -> List[int]:
if root == None:
return
a, b = [], []
pre = None
while root or a:
while root:
a.append(root)
root = root.left
res = a.pop()
if res.right == None or res.right == pre:
b.append(res.val)
pre = res
else:
a.append(res)
root = res.right
return b
时间复杂度:O(n),其中 n 为二叉树的节点数,遍历二叉树所有节点。
空间复杂度:O(n),辅助栈空间最大为链表所有节点数。
26.二叉树的层序遍历
26.1 借助队列
import queue
class Solution:
def levelOrder(self , root: TreeNode) -> List[List[int]]:
res = []
if root == None:
return
q = queue.Queue()
q.put(root)
cur = None
while not q.empty():
row = []
n = q.qsize()
for i in range(n):
cur = q.get()
row.append(cur.val)
if cur.left:
q.put(cur.left)
if cur.right:
q.put(cur.right)
res.append(row)
return res
时间复杂度:O(n),其中 n 为二叉树的节点数,每个节点访问一次。
空间复杂度:O(n),队列的空间为二叉树的一层的节点数,最坏情况二叉树的一层为 O(n) 级。
26.2 递归
step 1:首先判断二叉树是否为空,空树没有遍历结果。
step 2:使用递归进行层次遍历输出,每次递归记录当前二叉树的深度,每当遍历到一个节点,如果为空直接返回。
step 3:如果遍历的节点不为空,输出二维数组中一维数组的个数(即代表了输出的行数)小于深度,说明这个节点应该是新的一层,我们在二维数组中增加一个一维数组,然后再加入二叉树元素。
step 4:如果不是step 3的情况说明这个深度我们已经有了数组,直接根据深度作为下标取出数组,将元素加在最后就可以了。
step 5:处理完这个节点,再依次递归进入左右节点,同时深度增加。因为我们进入递归的时候是先左后右,那么遍历的时候也是先左后右,正好是层次遍历的顺序。
import sys
sys.setrecursionlimit(100000)
class Solution:
res = []
def traverse(self, root: TreeNode, depth: int):
if root:
# 新的一层
if len(self.res) < depth:
row = []
self.res.append(row)
row.append(root.val)
# 读取该层的一维数组,将元素加入到末尾
else:
row = self.res[depth-1]
row.append(root.val)
else:
return
# 递归时左右深度要加 1
self.traverse(root.left, depth+1)
self.traverse(root.right, depth+1)
def levelOrder(self , root: TreeNode) -> List[List[int]]:
if root == None:
return self.res
self.traverse(root, 1)
return self.res
时间复杂度:O(n),其中 n 为二叉树的节点数,每个节点访问一次。
空间复杂度:O(n),最坏二叉树退化为链表,递归栈的最大深度为 n。
27.按之字形顺序打印二叉树
27.1 借助队列
step 1:首先判断二叉树是否为空,空树没有打印结果。
step 2:建立辅助队列,根节点首先进入队列。不管层次怎么访问,根节点一定是第一个,那它肯定排在队伍的最前面,初始化flag变量。
step 3:每次进入一层,统计队列中元素的个数,更改flag变量的值。因为每当访问完一层,下一层作为这一层的子节点,一定都加入队列,而再下一层还没有加入,因此此时队列中的元素个数就是这一层的元素个数。
step 4:每次遍历这一层这么多的节点数,将其依次从队列中弹出,然后加入这一行的一维数组中,如果它们有子节点,依次加入队列排队等待访问。
step 5:访问完这一层的元素后,根据flag变量决定将这个一维数组直接加入二维数组中还是反转后再加入,然后再访问下一层。
import queue
class Solution:
def Print(self , pRoot: TreeNode) -> List[List[int]]:
res = []
if pRoot == None:
return
q = queue.Queue()
q.put(pRoot)
cur = None
flag = True
while not q.empty():
row = []
n = q.qsize()
flag = not flag
for i in range(n):
cur = q.get()
row.append(cur.val)
if cur.left:
q.put(cur.left)
if cur.right:
q.put(cur.right)
if flag:
row = list(reversed(row))
res.append(row)
return res
时间复杂度:O(n),每个节点访问一次,因为reverse的时间复杂度为O(n),按每层元素reverse也相
当于O(n)。
空间复杂度:O(n),队列的空间最长为O(n)。
27.2 双栈法
step 1:首先判断二叉树是否为空,空树没有打印结果。
step 2:建立两个辅助栈,每次依次访问第一个栈s1与第二个栈s2,根节点先进入s1。
step 3:依据依次访问的次序,s1必定记录的是奇数层,访问节点后,将它的子节点(如果有)依据先左后右的顺序加入s2,这样s2在访问的时候根据栈的先进后出原理就是右节点先访问,正好是偶数层需要的从右到左访问次序。偶数层则正好相反,要将子节点(如果有)依据先右后左的顺序加入s1,这样在s1访问的时候根据栈的先进后出原理就是左节点先访问,正好是奇数层需要的从左到右访问次序。
step 4:每次访问完一层,即一个栈为空,则将一维数组加入二维数组中,并清空以便下一层用来记录。
class Solution:
def Print(self , pRoot: TreeNode) -> List[List[int]]:
head = pRoot
res = []
if head is None:
return res
a, b = [], []
a.append(head)
while a or b:
temp = []
# 遍历奇数层
while a:
node = a[-1]
temp.append(node.val)
if node.left:
b.append(node.left)
if node.right:
b.append(node.right)
a.pop()
# 数组不为空才添加
if len(temp):
res.append(temp)
# 清空本层数据
temp = []
while b:
node = b[-1]
temp.append(node.val)
if node.right:
a.append(node.right)
if node.left:
a.append(node.left)
b.pop()
if len(temp):
res.append(temp)
return res
时间复杂度:O(n),其中 n 为二叉树的节点数,遍历二叉树的每个节点。
空间复杂度:O(n),两个栈的空间最坏情况为 n。
28.二叉树的最大深度
28.1 深度递归
class Solution:
def maxDepth(self , root: TreeNode) -> int:
if root == None:
return 0
return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1
时间复杂度:O(N), 每个节点需要遍历一次。
空间复杂度:O(N),最坏情况树退化为链表。
28.2 层序遍历
import queue
class Solution:
def maxDepth(self , root: TreeNode) -> int:
if root == None:
return 0
res = 0
q = queue.Queue()
q.put(root)
while not q.empty():
n = q.qsize()
for i in range(n):
node = q.get()
if node.left:
q.put(node.left)
if node.right:
q.put(node.right)
res = res + 1
return res
时间复杂度:O(n),其中 n 为二叉树的节点数,遍历整棵二叉树。
空间复杂度:O(n),辅助队列的空间最坏为 n。
29.二叉树中和为某一值的路径
29.1 递归
step 1:每次检查遍历到的节点是否为空节点,空节点就没有路径。
step 2:再检查遍历到是否为叶子节点,且当前sum值等于节点值,说明可以刚好找到。
step 3:检查左右子节点是否可以有完成路径的,如果任意一条路径可以都返回true,因此这里选用两个子节点递归的或。
class Solution:
def hasPathSum(self , root: TreeNode, sum: int) -> bool:
if root == None:
return False
if root.left == None and root.right == None and root.val == sum:
return True
return self.hasPathSum(root.left, sum-root.val) or self.hasPathSum(root.right, sum-root.val)
时间复杂度:O(n),其中 n 为二叉树所有节点,前序遍历二叉树所有节点。
空间复杂度:O(n),最坏情况二叉树化为链表,递归栈空间最大为 n。
29.2 非递归(深度优先搜索+辅助栈)
class Solution:
def hasPathSum(self , root: TreeNode, sum: int) -> bool:
# 空结点找不到路径
if root == None:
return False
# 栈辅助深度优先遍历
s = []
# 根结点入栈
s.append((root,root.val))
while len(s):
temp = s[-1]
s.pop()
# 如果它是叶子结点,且路径和等于 sum
if temp[0].left == None and temp[0].right == None and temp[1] == sum:
return True
# 左结点入栈
if temp[0].left:
s.append((temp[0].left, temp[1]+temp[0].left.val))
# 右结点入栈
if temp[0].right:
s.append((temp[0].right, temp[1]+temp[0].right.val))
return False
时间复杂度:O(n),其中 n 为二叉树所有节点,遍历二叉树所有节点。
空间复杂度:O(n),最坏情况二叉树化为链表,递归栈空间最大为 n。
30.二叉搜索树与双向链表
30.1 递归中序遍历
step 1:创建两个指针,一个指向题目中要求的链表头(head),一个指向当前遍历的前一节点(pre)。
step 2:首先递归到最左,初始化head与pre。
step 3:然后处理中间根节点,依次连接pre与当前节点,连接后更新pre为当前节点。
step 4:最后递归进入右子树,继续处理。
step 5:递归出口即是节点为空则返回。
class Solution:
head = None # 指向题目要求的链表头
pre = None # 指向当前遍历的前一结点
def Convert(self, pRootOfTree):
# 中序递归,叶子为空则返回
if pRootOfTree == None:
return None
# 首先递归到最左最小值
self.Convert(pRootOfTree.left)
# 找到最小值后,初始化 head 和 pre
if self.pre == None:
self.head = pRootOfTree
self.pre = pRootOfTree
# 当前结点与上一结点建立连接,将 pre 设置为当前值
else:
self.pre.right = pRootOfTree
pRootOfTree.left = self.pre
self.pre = pRootOfTree
self.Convert(pRootOfTree.right)
return self.head
30.2 非递归中序遍历
step 1:创建两个指针,一个指向题目中要求的链表头(head),一个指向当前遍历的前一节点(pre),创建一个布尔型变量,标记是否是第一次到最左,因为第一次到最左就是链表头。
step 2:判断空树不能连接。
step 3:初始化一个栈辅助中序遍历。
step 4:依次将父节点加入栈中,直接进入二叉树最左端。
step 5:第一次进入最左,初始化head与pre,然后进入它的根节点开始连接。
step 6:最后将右子树加入栈中,栈中依次就弹出“左中右”的节点顺序,直到栈为空。
class Solution:
def Convert(self, pRootOfTree):
if pRootOfTree == None:
return None
# 设置栈用于遍历
s = []
head = None
pre = None
# 确认第一个遍历到最左,即为首位
isFirst = True
while s or pRootOfTree:
# 走到最左
while pRootOfTree:
s.append(pRootOfTree)
pRootOfTree = pRootOfTree.left
pRootOfTree = s[-1]
s.pop()
# 首位
if isFirst:
head = pRootOfTree
pre = pRootOfTree
isFirst = False
# 当前结点与上一结点建立连接,将 pre 设置为当前值
else:
pre.right = pRootOfTree
pRootOfTree.left = pre
pre = pRootOfTree
pRootOfTree = pRootOfTree.right
return head
时间复杂度:O(n),其中 n 为二叉树节点数,中序遍历二叉树所有节点。
空间复杂度:O(n),栈 s 最大空间为为 O(n)。