2021秋招-数据结构-二叉树相关

文章目录

leetcode 树相关:67题(有重复)

模板总结

  1. 最常见3种遍历:递归和迭代
  2. 函数直接递归;
  3. 定义DFS函数,进一步进行递归;
  • 类型:
    • 是否型False/True;
    • 返回值型 int/float;
    • 返回值型 list
  • 要不要重新定义函数:
    • 递归参数是否可以复用? 如果不同,重新定义。
    • 递归返回值类型是否相同? 如果不同,必须重新定义;
    • 是不是有全局变量需要设置? 如果有全局变量,则新定义;
  • 使用方法:
    • 层次遍历+queue
    • DFS:前序、中序、后续
    • 迭代:前序、中序、后续
    • DFS+回溯:前序、中序、后续

二叉树算法的设计的总路线:明确一个节点要做的事情,然后剩下的事抛给框架。

def traverse(TreeNode root):
    // root 需要做什么?在这做。
	1) 处理顺序至关重要, 处理好可以简化很多操作; 
    // 其他的不用 root 操心,抛给框架
    traverse(root.left)
    traverse(root.right)

  • 层次遍历模板:
queue = [root]
while queue:
	# 每层节点数量; 
	queue_len = len(queue)
	
	# 队列中每个元素向四周扩展;
	for i in range(queue_len):
		# 记住: 一定一定, 队列pop(0)!!!!!
		curNode = queue.pop(0)
		
		# 当前节点进一步处理: 1) 记录; 2) 满足性; 3) 终点判断、异常判断; 
		pass

		# 当前元素向四周扩展;  有时候需要根据题目判断满足性; 
		queue.append(curNode.left)
		queue.append(curNode.right)
	
  • 非递归, 迭代方式;

三种非递归遍历

旧思路三种遍历统一框架

二叉树的遍历都可以借助栈结构使用DFS算法完成。
首先是最简单的先序遍历,**父>左>右。**见144题 。
每次入栈前先将父节点加入结果列表,然后左节点入栈。
当左子树遍历完后,再遍历右子树。

  • 先序遍历,父>左>右
# 先序遍历,父>左>右
class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        res = []  #结果列表
        stack = []  #辅助栈
        cur = root  #当前节点
        while stack or cur:	# cur 和 stack 一个满足就继续; 
            while cur:  #一直遍历到最后一层
                res.append(cur.val)  
                stack.append(cur)
                cur = cur.left
            top = stack.pop()  #此时该节点的左子树已经全部遍历完
            cur = top.right  #对右子树遍历
        return res
  • 后序遍历,左>右>父
后序遍历,左>右>父。见145题 。
能不能借助先序遍历的思路来呢,我们将上面的顺序翻转过来得到,父>右>左。
所以现在可以按照之前的方法遍历,最后把结果翻转一下。
# 后序遍历,左>右>父  
class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        stack = []
        cur = root
        while stack or cur:
            while cur:
                res.append(cur.val)
                stack.append(cur)
                cur = cur.right  #先将右节点压栈
            top = stack.pop()  #此时该节点的右子树已经全部遍历完
            cur = top.left  #对左子树遍历
        return res[::-1]  #结果翻转
  • 中序遍历, 左>父>右
中序遍历, 左>父>右。见94题 。
与先序遍历不同的是,出栈时才将结果写入列表。
class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        stack = []
        cur = root
        while stack or cur:
            while cur:
                stack.append(cur)
                cur = cur.left
            top = stack.pop() #此时左子树遍历完成
            res.append(top.val)  #将父节点加入列表
            cur = top.right #遍历右子树
        return res

三种递归遍历

# 先序遍历
def preorderTraversal(self, root: TreeNode) -> List[int]:
	if not root:
		return None
	print(root.val)
	self.preorderTraversal(root.left)
	self.preorderTraversal(root.right)

# 中序遍历
def midOrder(root):
	if not root:
		return None
	self.midOrder(root.left)
	print(root.val)
	self.midOrder(root.right)

# 后序遍历
def postOrder(root):
	if not root:
		return None
	self.postOrder(root.left)
	self.postOrder(root.right)
	print(root.val)

python实现树形DP,一般都是后序遍历,理由上述

class Solution:
	def rob(self, root: TreeNode) -> int:
		return max(self.dp(root))
	
	def dp(curRoot):
		if not curRoot:
			return [0, 0]
		
		# 后序遍历
		left = self.dp(curNode.left)
		right = self.dp(curNode.right)

		dp = [0, 0]
		# dp[0]:以当前 node 为根结点的子树能够偷取的最大价值,规定 node 结点不偷
		# dp[1]:以当前 node 为根结点的子树能够偷取的最大价值,规定 node 结点偷
		dp[0] = max(left[0], left[1]) + max(right[0], right[1]) 
		dp[1] = curRoot.val + left[0] + right[0] 

模仿递归三种遍历

完全模仿递归,不变一行。秒杀全场,一劳永逸

新思路

递归的本质就是压栈,了解递归本质后就完全可以按照递归的思路来迭代。
怎么压,压什么?压的当然是待执行的内容,后面的语句先进栈,所以进栈顺序就决定了前中后序。
我们需要一个标志区分每个递归调用栈,这里使用nullptr来表示。
具体直接看注释,可以参考文章最后“和递归写法的对比”。先序遍历看懂了,中序和后序也就秒懂。
  • 先序遍历
# 先序遍历: 自己未看
def preorderTraversal(self, root: TreeNode) -> List[int]:
        if root is None: return []                       # 首先介入root节点
        result = []
        stack = [root]
        while stack:
            p = stack.pop()		# 访问过节点弹出
            if p is None:
                p = stack.pop()
                result.append(p.val)
            else:
                if p.right: 					   # 右节点先压栈,最后处理
                	stack.append(p.right)          # 先append的最后访问
                if p.left: 
                	stack.append(p.left)
                stack.append(p)				      # 当前节点重新压栈(留着以后处理),因为先序遍历所以最后压栈
                stack.append(None)                # 在当前节点之前加入一个空节点表示已经访问过了
        return result       
  • 中序遍历
# 中序遍历
			else:
                if p.right: 
                	stack.append(p.right)
                stack.append(p)
                stack.append(None)
                if p.left: 
                	stack.append(p.left)
  • 后序遍历
			else:
                stack.append(p)
                stack.append(None)
                if p.right: 
                	stack.append(p.right)
                if p.left: 
                	stack.append(p.left)

⭐LeetCode刷题总结-树篇(上)

链接

在LeetCode的标签分类题库中,和树有关的标签有:树(123道题)、字典树(17道题)、线段树(11道题)、树状数组(6道题)。对于这些题,作者在粗略刷过一遍后,对其中的考点进行了总结,并归纳为以下四大类:

  • 树的自身特性
  • 树的类型
  • 子树问题
  • 新概念定义问题
    在这里插入图片描述

一、 树基本特性问题

Offer 32 - I. 从上到下打印二叉树-中等-层次遍历

leetcode链接
在这里插入图片描述

  • 层次遍历思路:
class Solution:
    def levelOrder(self, root: TreeNode) -> List[int]:
        # 思路: 二叉树的层次遍历; 
        res = []
        queue = [root]
        while queue:
            queue_len = len(queue)
            for i in range(queue_len):
                curNode = queue.pop(0)
                # 当前元素加入
                if curNode:
                    res.append(curNode.val)

                    queue.append(curNode.left)
                    queue.append(curNode.right)
        return res

Offer 32 - II. 从上到下打印二叉树 II

leetcode链接
在这里插入图片描述

  • 层次遍历
class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        res = []
        queue = [root]
        while queue:
            lines = []
            queue_len = len(queue)
            for i in range(queue_len):
                curNode = queue.pop(0)
                if curNode:
                    lines.append(curNode.val)
                    queue.append(curNode.left)
                    queue.append(curNode.right)
            if lines:
                res.append(lines)
        return res

在这里插入图片描述

32-III.从上到下打印二叉树III-之字型

leetcode链接
在这里插入图片描述

  • 层次遍历
class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        res = []
        queue = [root]
        count = 0
        while queue:
            count += 1
            lines = []
            queue_len = len(queue)
            for i in range(queue_len):
                curNode = queue.pop(0)
                if curNode:
                    lines.append(curNode.val)
                    queue.append(curNode.left)
                    queue.append(curNode.right)
            if count%2 == 0:
                lines = lines[::-1]
            if lines:
                res.append(lines)
        return res

Offer28. 对称的二叉树-lc101
leetcode链接

101-对称二叉树-简单

leetcode链接
在这里插入图片描述

  • 思路1: 递归思路 : 深度优先遍历: 100
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        # 类型: 1) 是否型;  2)重新定义: 自身不满足;  ;  3) 递归; 

        def isMirror(root1, root2):    
            '''
            判断两个二叉树是否镜像; 
            '''
            # 框架: 
            # 步骤1: root需要做什么?  1) 满足条件判断;   2) 不满足条件判断: a:剪枝; b:退出; 
            # 步骤2:其他y不用root操心, 交给框架; 

            # root做什么? 
            # 两种写法:  True / False类型;  1)哪种情况满足; 2) 哪种情况不满足;
            
            # 两个都是空节点: 必定相等; 
            if not root1 and not root2:
                return True
            
            # 一个空,一个非空,必定不等; 
            if not root1 or not root2:
                return False

            # 交给框架:  1)  val相等;  2) 左;   3) 右; 
            return root1.val == root2.val and isMirror(root1.left, root2.right) and isMirror(root1.right, root2.left)
        
        # 可以省略; 
        '''
        # 异常判断; 
        if not root:
            return False
        '''
        return isMirror(root, root)
  • 思路2:迭代思路: 层次遍历
class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        # 思路2: 层次遍历: 镜像特点: 沿中间到两半对称;
        queue = [root]
        while queue:
            queue_len = len(queue)
            row_res = []

            # 队列每个元素向四周扩展;
            for i in range(queue_len):
                # 记住了: 一定一定一定 pop(0)
                curNode = queue.pop(0)
                
                # 每行元素记录:
                if curNode:
                    row_res.append(curNode.val)
                else:
                    row_res.append('null')

                if curNode:
                    # 当前元素四周扩展;  **可能添加了: null
                    queue.append(curNode.left)
                    queue.append(curNode.right)

            # while queue一次循环: 遍历一行结果; 
            # 满足性质判断;
            reverse_row_res = row_res[::-1]
            if reverse_row_res != row_res:
                return False
        
        return True

❌971.翻转二叉树以匹配前序遍历-中等

❌655.出二叉树-中等-不做了,求坐标就行

在这里插入图片描述

class Solution:
    def printTree(self, root: TreeNode) -> List[List[str]]:
        # 思路: 直接层次遍历,完了再来一遍进行转换? 
        # 思考难点: 1) 二叉树深度不知道 -> 数组 m, n不清楚;  n = 2^m -1; 
        # 2) 层级关系填充,想考察: 节点坐标关系? 

        def BFS(root):
            queue = [root]
            while queue:
                row_res = []
                queue_len = len(queue)
                # 队列中元素向四周扩展; 
                for i in range(queue_len):
                    # pop(0) !!!
                    curNode = queue.pop(0)

                    if curNode:
                        row_res.append(curNode.val)
                    else:
                        row_res.append('null')
                    
                    if curNode:
                        queue.append(curNode.left)
                        queue.append(curNode.right)
                res.append(row_res)
        
        if not root:
            return []
        res = []
        BFS(root)
        print(res)
        没做完----

617-合并二叉树-简单

leetcode链接
在这里插入图片描述

  • 深度优先搜索
class Solution:
    def mergeTrees(self, t1: TreeNode, t2: TreeNode) -> TreeNode:

        # 类型: 1) 非是否型;  2) 自身调用;  3) 递归; 

        # 框架:
        # 步骤1: root做点什么? 
        # 步骤2: 框架左右
        # 步骤3: 返回

        # 步骤1: 根节点值相加; 
        if not t1:
            return t2
        if not t2:
            return t1
        t1.val = t1.val + t2.val
        
        '''
        这块自己第一次i写错了,顺序没有考虑对,!!!
        !!!处理好顺序,可能更容易; 
        elif t1 and not t2:
            t1.val = t1.val
        elif not t1 and t2:
            t1.val = t2.val
        else:
            return None
        '''

        # 步骤2:左右框架; 
        left = self.mergeTrees(t1.left, t2.left)
        right = self.mergeTrees(t1.right, t2.right)

        # 返回结果处理
        t1.left = left
        t1.right = right

        # 返回
        return t1

814.二叉树剪枝-中等-后序遍历

leetcode链接

  • 大佬后序遍历: 剪枝方法;
class Solution:
    def pruneTree(self, root: TreeNode) -> TreeNode:
        # 明确问题:二叉树剪支,减掉,值为0且无子树的节点,值为0且子树的节点值均为0
        # 解题思路:后序遍历,判断当前节点值是否为0,左右子节点,是否为空,均是则减掉
        if not root:
            return None
        root.left = self.pruneTree(root.left)
        root.right = self.pruneTree(root.right)
        
        # 剪枝: 自底向上, 去掉叶子节点, 且为0; 
        # 放在此处: 后序遍历: 先处理左右,完了再处理当前; 
        if root.val == 0 and not root.left and not root.right:
            return None
        return root
  • 自己实现
class Solution:
    def pruneTree(self, root: TreeNode) -> TreeNode:
        # 思路: 深度优先搜索, 记录对应子树是否包含1; 
        # 递归返回的值:  0 或者 1;  自己做时候没想明白这个; 
        
        def prune(root):
            # 空节点; 
            if not root:
                return 0
            # 非空节点; 
            if not root.left and not root.right:
                return root.val
            
            left = prune(root.left)
            right = prune(root.right)

            if left == 0:
                root.left = None    
            if right == 0:
                root.right = None    
        
            return root.val or left or right

        prune(root)
        # 这种傻逼题目....
        if root.val == 0 and not root.left and not root.right:
            return None
        return root

199.二叉树的右视图-中等

leetcode链接

  • 解题思路:
    层次遍历的实际应用。只需依次保存每层最右边的一个节点即可。
  • 具体代码:
class Solution:
    def rightSideView(self, root: TreeNode) -> List[int]:
        # 思路: 层次遍历; 
        res = []
        queue = [root]
        while queue:
            line = []
            queue_len = len(queue)
            for _ in range(queue_len):
                curNode = queue.pop(0)

                if curNode:
                    line.append(curNode.val)
                    queue.append(curNode.left)
                    queue.append(curNode.right)
            if line:
                res.append(line[-1])
        return res

111.二叉树的最小深度-简单

leetcode链接

  • 层次遍历思路:
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def minDepth(self, root: TreeNode) -> int:
        # BFS: 时间复杂度相对DFS较低, 但是空间复杂度O(N) > DFS:O(log N)
        if not root:
            return 0
        
        queue = [root]          # BFS使用队列
        depth = 1               # 初始化高度/深度; 可以随后面调整; 
        while queue:
            # 记录队列中元素数量: 二叉树每一层node数量
            queue_size = len(queue)
            
            # 遍历二叉树一层node, 并将周围节点加入 队列
            for i in range(queue_size):
                # 取最先进入队列元素
                curNode = queue.pop(0)
                
                # 判断是否到达终点
                if not curNode.left and not curNode.right:
                    return depth
                
                # 将当前节点周围node加入队列
                if curNode.left:
                    queue.append(curNode.left)
                if curNode.right:
                    queue.append(curNode.right)
            
            # 遍历一层, 深度 +1
            depth += 1
-------------------------------------------------------------------------------------------------
class Solution:
    def minDepth(self, root: TreeNode) -> int:
        # 思路1: 层次遍历
        if not root:
            return 0
        queue = [root]
        res = 0
        while queue:
            res += 1
            queue_len = len(queue)
            for _ in range(queue_len):
                curNode = queue.pop(0)

                if curNode:
                    queue.append(curNode.left)
                    queue.append(curNode.right)
                    # 必须满足左右子树都为空才可以是叶子节点; 
                    if not curNode.left and not curNode.right:
                        return res
  • 深度优先搜索思路
class Solution:
    def minDepth(self, root: TreeNode) -> int:
        # 思路:深度优先DFS;  后续遍历; 
        if not root:
            return 0
        
        left = self.minDepth(root.left)
        right = self.minDepth(root.right)

        # 计算最小深度: 坑; 
        # 深度定义必须到叶子节点,所以中间节点不算; 
        if root.left and root.right:
            return min(left, right) + 1
        else:
            return 1 + left + right

Offer 55 - I. 二叉树的深度

104. 二叉树的最大深度

leetcode链接
在这里插入图片描述

  • 递归思路:
class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        # 思路: 后续遍历; 
        if not root:
            return 0
        
        left = self.maxDepth(root.left)
        right = self.maxDepth(root.right)

        return max(left, right) + 1
  • 层次遍历思路: 累加

662.二叉树的最大宽度-中等

leetcode链接
在这里插入图片描述

  • BFS思路, 难点在于:下标计算和表示;
class Solution:
    def widthOfBinaryTree(self, root: TreeNode) -> int:
        # 思路: BFS, 记录对应节点下标即可;   
        # 坐标对应: left=2*1+1, right = 2*i+2;  
        if not root:
            return 0
        
        queue = [(0, root)]
        res = 0
        while queue:
            queue_len = len(queue)
            res = max(res, queue[-1][0] - queue[0][0]+1)
            for i in range(queue_len):
                i, curNode = queue.pop(0)
                # 当前元素向四周扩散; 
                if curNode.left:
                    queue.append((2*i+1, curNode.left))
                if curNode.right:
                    queue.append((2*i+2, curNode.right))
            
        return res

二、构造二叉树

889.依据前序和后序遍历构造二叉树-中等

leetcode链接
在这里插入图片描述

根据前序和后序遍历构造二叉树
在这里插入图片描述在这里插入图片描述

  • 递归思路
class Solution:
    def constructFromPrePost(self, pre: List[int], post: List[int]) -> TreeNode:
        # 前序+后序 构造二叉树数量很大, 题目要求返回一个即可; 按照常见思路思考即可; 
        # 思路: 1) 返回TreeNode; 2) 无需全局变量;  3)自身调用; 

        # 框架1: root做点什么?
        if not pre or not post:
            return None
        
        # 根据题目调试,pre[1]越界添加;
        if len(pre) == 1:
            return TreeNode(pre[0])
        
        root = TreeNode(pre[0])
        left_root = pre[1]
        index = post.index(left_root)

        # 框架2: 左右子树;
        left = self.constructFromPrePost(pre[1:index+2], post[:index+1])
        right = self.constructFromPrePost(pre[index+2:], post[index+1:-1])

        # 框架3:后序遍历:返回值进一步处理;
        root.left = left
        root.right = right

        # 框架4:根节点返回;
        return root

1008. 先序遍历构造二叉树-中等

leetcode链接
在这里插入图片描述

  • 先序+中序:递归思路
class Solution:
    def bstFromPreorder(self, preorder: List[int]) -> TreeNode:
        # 二叉搜索树: 中序遍历=排序; 相当于 先序+中序; 
        # 思路:1) 返回TreeNode;  2) 无需全局变量;  3)重新定义;
        inorder = sorted(preorder)

        def buildTree(preorder, inorder):
            # 框架1: root做点什么? 
            if not preorder or not inorder:
                return None
            root = preorder[0]
            index =inorder.index(root)
            root = TreeNode(root)

            # 框架2: 左右子树;
            left = buildTree(preorder[1:index+1], inorder[:index])
            right = buildTree(preorder[index+1:], inorder[index+1:])

            # 框架3: 后序遍历: 返回结果处理
            root.left = left
            root.right = right

            # 框架4: 返回子树根节点;
            return root
        return buildTree(preorder, inorder)

1028.从先序遍历还原二叉树-困难-迭代模型回溯

leetcode链接
+

在这里插入图片描述

  • 栈模型:迭代+回溯过程;
class Solution:
    def recoverFromPreorder(self, S: str) -> TreeNode:
        # 思路: 1)返回TreeNode;   2)需全局变量;  3) 不是递归调用; 

        # 全局变量path, 用来模拟回溯过程;
        path = []
        # 全局变量pos, 记录字符串中的位置;
        pos = 0

        while pos < len(S):
            # 获取高度;
            level = 0
            while S[pos] == '-':
                level += 1
                pos += 1
            
            # 获取当前节点;
            value = 0
            # pos<len(S):防止越界;
            while pos < len(S) and S[pos].isdigit():
                value = value*10 + int(S[pos])
                pos += 1
            root = TreeNode(value)

            # 当前节点连接到父 前节点;
            # 左子树;
            if level == len(path):
                if path:
                    path[-1].left = root
            # 右子树;
            else:
                # 模拟回溯过程: path[:level]找到当前父节点、右子树;
                path = path[:level]
                path[-1].right = root
            path.append(root)
        return path[0]

105. 从前序与中序遍历序列构造二叉树-中等

leetcode链接
在这里插入图片描述

  • 后序遍历, 递归思路
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        # 步骤1: 思路: 1)返回TreeNode;  2)无需全局变量;  3) 自身调用; 
        
        # 异常判断: 可有可无; 
        if len(preorder) != len(inorder):
            return None

        # 框架1: root做点什么? 
        if not preorder or not inorder:
            return None
        root = preorder[0]
        index = inorder.index(root)
        root = TreeNode(root)

        # 框架2: 左右子树
        left = self.buildTree(preorder[1:index+1], inorder[:index])
        right = self.buildTree(preorder[index+1:], inorder[index+1:])

        # 框架3: 后序:递归结果进一步处理; 
        root.left = left
        root.right = right

        # 框架4: 返回当前根节点;
        return root

106. 从中序与后序遍历序列构造二叉树-中等

leetcode链接
在这里插入图片描述

  • 后序遍历,递归思路
class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
        # 思路: 1) 返回TreeNode;  2)无需全局变量;  3)自身调用; 
        
        # 框架1: root做点什么
        if not inorder or not postorder:
            return None
        root = postorder[-1]
        index = inorder.index(root)
        root = TreeNode(root)
        
        # 框架2: 左右子树;
        left = self.buildTree(inorder[:index], postorder[:index])
        right = self.buildTree(inorder[index+1:], postorder[index:-1])

        # 框架3: 后序遍历结果处理
        root.left = left
        root.right = right

        # 框架4: 返回当前子树根节点;
        return root

三、节点问题

Offer 68 - I. 二叉搜索树的最近公共祖先

Offer 68 - II. 二叉树的最近公共祖先

236-二叉树的最近公共祖先-中等

leetcode链接
在这里插入图片描述

  • 思路: 直接递归
  • 直接递归和使用定义dfs区别:
class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        # 类型: 1) 返回TreeNode;  2)后序遍历;  3)自身调用;  4)无全局变量;

        # 框架1: root做点什么? 
        if not root:
            return None
        if root == p or root == q:
            return root
        
        # 框架2: 左右子树;
        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)

        # 框架3/4: 后序遍历:返回值进一步处理;
        if left and right:
            return root
        if left:
            return left
        if right:
            return right
        return None

337.打家劫舍 III-中等

leetcode链接
在这里插入图片描述

class Solution:
    def rob(self, root: TreeNode) -> int:
        # 类型: 1) 返回int; 2无需全局变量; 3)重新定义;  4)后序遍历;
        def maxRob(root):
            # 框架1: root做点什么?
            if not root:
                return [0, 0]
            # 框架2: 左右子树;
            left = maxRob(root.left)
            right = maxRob(root.right)

            # 框架3:后序遍历返回结果处理
            dp = [0, 0] # [当前节点偷, 当前节点不偷]
            # case1:当前节点不偷;
            dp[0] = max(left[0], left[1]) + max(right[0], right[1])
            # case2: 当前节点偷; 
            dp[1] = left[0] + right[0] + root.val

            # 框架4: 返回
            return dp

        return max(maxRob(root))

❌623.在二叉树中增加一行-中等

863.二叉树中所有距离为K的节点-中等

leetcode链接
在这里插入图片描述
在这里插入图片描述

  • DFS:每个节点加入父亲节点; 转换为图; 然后进行BFS,查找距离为1节点;
class Solution:
    def distanceK(self, root: TreeNode, target: TreeNode, K: int) -> List[int]:
        # 思路:1.添加parentPointer构建有向图; 2.BFS:向四周遍历K步; 
        # 类型: 1) 返回list;  2) 无需全局变量;  3) 非递归;  4) 层次遍历;
        def dfs(root, par= None):
            # 框架1: root做点什么? 
            if not root:
                return None
            root.parent = par

            # 框架2: 左右子树;
            dfs(root.left, root)
            dfs(root.right, root)
        dfs(root)

        # BFS进行层次遍历,查找K步; 
        queue = [(target, 0)]
        # visited: 防止父节点重复访问;
        visited = [target] 
        while queue:
            # queue.pop(0) 之前对queue内元素进行满足性判断; 
            if queue[0][1] == K:
                return [node.val for node, dist in queue]

            queue_len = len(queue)
            # 队列元素向四周扩撒;
            for _ in range(queue_len):
                curNode, dist = queue.pop(0)

                # 当前元素向四周扩撒;
                for direction in (curNode.left, curNode.right, curNode.parent):
                    # 满足性条件检查;
                    if direction and direction not in visited:
                        queue.append((direction, dist+1))
                        visited.append(direction)
        return []

968.监控二叉树-困难⭐

leetcode链接

在这里插入图片描述

  • 如果左右子节点都无法被监控到 则当前放置一个摄像头

  • 如果左右子节点能够被监控到,则当前返回无法被监控

  • 如果左右子节点有一个有摄像头,则当前返回可以被监控到

  • 叶子节点始终返回无法被监控到。 叶子节点不能放摄像头

  • 递归+贪心

class Solution:
    def minCameraCover(self, root: TreeNode) -> int:
        # 类型: 1)返回int; 2)需全局变量; 3)重新定义; 4) 后序遍历;
        # 思路: 贪心 + 后序遍历; 
        # 定义三种状态: 
        # 0:未被覆盖(当前节点未被照到);
        # 1:已被覆盖(摄像头照到该节点);
        # 2: 需要放置摄像头;

        # 如果左右节点都无法监控到:放置摄像头;
        # 如果左右节点都可以被监控到: 当前节点无法监控; 
        # 左右节点有一个摄像头:返回当前节点可以被监控;
        # 叶子节点返回始终被监控:叶子节点不能放置摄像头;

        def dfs(root):
            # return:对于每个节点返回3种状态
            # 框架1: root做点什么? 
            if not root:
                return 1
            # 框架2: 左右子树
            left = dfs(root.left)
            right = dfs(root.right)

            # 框架3/4: 后序遍历结果处理; 
            # case1: 左右有一个未被监控,则放置摄像头;
            if left == 0 or right == 0:
                self.res += 1
                return 2
            # case2:左右都已覆盖,则当前节点未覆盖;
            if left == 1 and right == 1:
                return 0
            # case3: 左右一个覆盖、至少一个摄像头(至少), 则当前节点覆盖;
            if left + right >= 3:
                return 1
        self.res = 0
        tmp = dfs(root)
        # 递归最后:对于最顶层节点,如果返回0,说明顶层节点没有被覆盖, +1摄像头;
        return self.res + 1 if tmp == 0 else self.res

1145-二叉树着色游戏-中等-不做了

四、路径问题

  • 模板:
# 确定类型: 
def dfs():
	1.root
	2. left - right
	3. 后序遍历进一步处理
	4. 返回

112. 路径总和

leetcode链接
在这里插入图片描述

路径定义: 根节点到子节点;  所以递归找到子节点再判断; 

class Solution:
    def hasPathSum(self, root: TreeNode, sum: int) -> bool:
       
        def dfs(root, target):
            '''
            递归找目标值
            '''
            # dfs-3步骤
            # 其实已经判断, 不必要再判断
            if not root:
                return False

            # 满足条件判断: 1) 找到目标值; 2)必须是叶子节点; 
            if sum == target and not root.left and not root.right:
                return True
            
            # 右子树找满足条件
            if root.right:
                if dfs(root.right, target+root.right.val):
                    return True

            # 左子树找满足条件;
            if root.left:
                if dfs(root.left, target+root.left.val):
                    return True

            return False
        
        if not root:
            return False
        
        return dfs(root, root.val)
  • target设置2
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def hasPathSum(self, root: TreeNode, sum: int) -> bool:
        # 思路: 递归dfs求路径和, 判断路径和是不是等于 sum; 
        def dfs(root, target):
            '''
            计算根节点到当前节点的路径和; 
            '''
            if not root:
                return False
            # 满足条件判断; 
            target = target + root.val
            # 1) 等于目标和; 2)叶子节点; 
            if target == sum and not root.left and not root.right:
                return True
            # 左子树是否满足条件;
            if root.left:
                if dfs(root.left, target):
                    return True
            # 右子树是否满足条件; 
            if root.right:
                if dfs(root.right, target):
                    return True
            
            # 如果左右都False;
            return False

        if not root:
            return False

        return dfs(root, 0)        
  ---------------------------------------------------------------------------------------------
  # 推荐
  class Solution:
    def hasPathSum(self, root: TreeNode, sum: int) -> bool:
        # 类型: 1) bool; 2)无需全局变量; 3)重新定义; 4) 后序遍历;
        def dfs(root, target):
            # 框架1: root做点什么? 
            if not root:
                return False
            # 满足条件判断
            target = target + root.val
            if sum == target and not root.left and not root.right:
                return True
            
            # 框架2: 左右子树
            left = dfs(root.left, target)
            right = dfs(root.right, target)

            # 框架3/4: 后序遍历结果处理; 
            return left or right
        return dfs(root, 0)

113. 路径总和 II

leetcode链接
在这里插入图片描述

  • 仍然套回溯模板;
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:
        # 思路: 回溯记录path; 回溯和dfs思路和实现一致,但是要对于路径回溯; 
        def dfs(root, target):
            # 3步骤; 
            # 满足条件判断
            target = target + root.val
            # 1) 等于目标和; 2)
            if target == sum and not root.left and not root.right:
                res.append(path[:])

            # 最好判断: root.left 否则可能: root.left.val 不存在;
            if root.left:
                path.append(root.left.val)
                dfs(root.left, target)
                path.pop()

            if root.right:
                path.append(root.right.val)
                dfs(root.right, target)
                path.pop()
            
        if not root:
            return []
        res = []
        path = [root.val]
        dfs(root, 0)
        return res
---------------------------------------------------------------------------------------------
# 推荐
class Solution:
    def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:
        # 类型: 1) 返回list; 2)全局变量; 3)重新定义;  4)后序遍历;
        def dfs(root, target):
            # 框架1: root做点什么? 
            if not root:
                return []
            target = target + root.val
            path.append(root.val)
            # 满足条件判断;
            if sum == target and not root.left and not root.right:
                res.append(path[:])

            # 框架2: 左右子树
            left = dfs(root.left, target)
            right = dfs(root.right, target)
            path.pop()
            # 框架3/4:后序遍历,进一步处理
        res = []
        path = []
        dfs(root, 0)
        return res

437. 路径总和 III

leetcode链接
在这里插入图片描述

  • 继续套模板;
  • 局部变量、全局变量、可变变量、不可变变量: 继续看看
class Solution:
    def pathSum(self, root: TreeNode, sum: int) -> int:
        # 1. 不需要保存路径,不用对path回溯; 
        # 2. 不限制起点, BFS全部试一遍;
        # 3. 不限制终点, 不必判断是否叶子节点;
        # 类型: 1) 返回int; 2)全局变量; 3)重新定义; 4) 后序遍历;
        def dfs(root, target):
            # 框架1: root做点什么? 
            if not root:
                return 
            target = target + root.val
            # 满足条件判断
            if sum == target:
                self.res += 1
            
            # 框架2: 左右子树;
            left = dfs(root.left, target)
            right = dfs(root.right, target)
        
        if not root:
            return 0
        # 起点没有限制,所有节点遍历一遍;
        # BFS遍历:不用dfs, 思考简单;
        queue = [root]
        self.res = 0
        while queue:
            queue_len = len(queue)
            for _ in range(queue_len):
                curNode = queue.pop(0)
                
                if curNode:
                    dfs(curNode, 0)
                    queue.append(curNode.left)
                    queue.append(curNode.right)
        return self.res

257. 二叉树的所有路径

leetcode链接
在这里插入图片描述

  • 套模板
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def binaryTreePaths(self, root: TreeNode) -> List[str]:
        # 思路: 回溯

        def dfs(root):
            if not root:
                return 
            
            if not root.left and not root.right:
                res.append('->'.join([str(i) for i in path]))
                return

            # 左子树; 判断存在性: root.left.val 存在性; 
            if root.left:
                path.append(root.left.val)
                dfs(root.left)
                path.pop()
            
            # 右子树
            if root.right:
                path.append(root.right.val)
                dfs(root.right)
                path.pop()
        res = []
        if not root:
            return []
        path = [root.val]
        dfs(root)

        return res
 -------------------------------------------------------------
 # 推荐
 class Solution:
    def binaryTreePaths(self, root: TreeNode) -> List[str]:
        # 类型: 1)返回list; 2) path全局变量; 3) 重新定义; 4) 后序遍历;
        def dfs(root):
        	# 无用返回值
            # 框架1: root做点什么? 
            if not root:
                return []
            # 满足条件判断
            path.append(root.val)
            if not root.left and not root.right:
                res.append('->'.join([str(num) for num in path]))
                path.pop()  # 特别之处; 
                return
    
            # 框架2: 左右子树;
            left = dfs(root.left)
            right = dfs(root.right)
            path.pop()
        res = []
        path = []
        dfs(root)
        return res

124.二叉树中的最大路径和-困难

leetcode链接
在这里插入图片描述

class Solution:
    def maxPathSum(self, root: TreeNode) -> int:
        # 路径和定义: 起点任意, 终点任意; 
        # 路径: 对于当前节点如果走其父节点, 则max(left, right)
        # 如果当前为根节点, left + right + root.val
        # 类型: 1)返回int; 2)全局变量; 3)重新定义; 4) 后序遍历;
        def dfs(root):
            # 框架1: root做点什么?
            if not root:
                return 0
            # 框架2: 左右子树;
            left = dfs(root.left)
            right = dfs(root.right)

            # 框架3: 后序遍历结果处理
            curMax = max(left, right, 0) + root.val
            # 更新全局变量;
            # 1)最大值不更新,当前节点不经过;
            # 2)路过该节点,选择左、右一个;
            # 3)当前节点为根节点;
            self.max_path_sum = max(self.max_path_sum, left+right+root.val, curMax)

            # 框架4: 返回当前子树根节点条件下最大值;
            return curMax
        self.max_path_sum = float('-inf')
        dfs(root)
        return self.max_path_sum

543.二叉树的直径

leetcode链接
在这里插入图片描述

  • 类似于:二叉树最大路径和124思路;
class Solution:
    def diameterOfBinaryTree(self, root: TreeNode) -> int:
        # 和最大路径和一样,区别: 直径全为正; 
        # 类型: 1)返回int; 2)全局变量; 3)重新定义; 4)后序遍历;
        def dfs(root):
            # 框架1:root做点什么? 
            if not root:
                return 0
            # 框架2:左右子树;
            left = dfs(root.left)
            right = dfs(root.right)

            # 框架3: 后序遍历结果进一步处理; left+right+1 > curMax
            curMax = max(left, right) + 1
            self.res = max(self.res, left+right+1)

            # 框架4: 返回结果;
            return curMax
        if not root:
            return 0
        self.res = 0
        dfs(root)
        return self.res-1

687. 最长同值路径

leetcode链接
在这里插入图片描述

  • 类似124最大路径和;
class Solution:
    def longestUnivaluePath(self, root: TreeNode) -> int:
        def dfs(root, par=None):
            # 框架1: root做点什么? 
            if not root:
                return 0
            
            # 框架2: 左右子树
            left = dfs(root.left, root)
            right = dfs(root.right, root)

            # 框架3: 后序遍历结果处理
            curValue = 0
            if par.val == root.val:
                curValue = max(left, right) + 1
            # 不用+1: 求边数; 
            self.res = max(self.res, left+right)

            # 框架4: 返回
            return curValue
        self.res = 0
        dfs(root, root)
        return self.res

979.二叉树中分配硬币-中等

leetcode链接
在这里插入图片描述

在这里插入图片描述

  • 类似124思路;
class Solution:
    def distributeCoins(self, root: TreeNode) -> int:
        # 类型: 1) 返回int;  2)全局变量; 3) 重新定义; 4) 后序遍历; 
        # 思路: 自己没有想明白移动次数怎么表示, 尤其是在递归过程中怎么计算; 
        # 1) 使用 过载量 的概念来表示,当前节点向上父节点,或者父节点到自己移动次数;
        # 2) 全局变量: 每个节点都计算其左右的移动次数求和; 

        def dfs(root):
            # 框架: root做点什么? 
            if not root:
                return 0
            
            # 框架2: 左右子树; 
            left = dfs(root.left)
            right = dfs(root.right)

            # 框架3: 后序遍历:结果处理; 
            # 全局移动次数: 每个节点为根节点子树的之间(不涉及向父节点)移动次数; 
            self.res += abs(left) + abs(right)

            # 框架4: 返回当前节点的过载量; 
            return root.val + left + right - 1
        self.res = 0
        dfs(root)
        return self.res

987.二叉树的垂序遍历-中等

leetcode链接
在这里插入图片描述

  • 太牛逼了! DFS记录坐标,然后排序:
class Solution:
    def verticalTraversal(self, root: TreeNode) -> List[List[int]]:
        # 思路: DFS记录坐标进行排序
        # 类型: 1) 返回list;  2)全局变量:记录访问路径;  3)重新定义;  4)先序遍历; 
        lst = []
        def dfs(root, x= 0, y=0):
            # 框架1: root做点什么? 
            if not root:
                return 
            lst.append((x, y, root.val))
            # 框架2: 左右子树
            left = dfs(root.left, x-1, y-1)
            right = dfs(root.right, x+1, y-1)
            # 框架3/4
        dfs(root, 0, 0)

        # 结果进一步排序; -x[1]:y坐标逆序;
        lst = sorted(lst, key= lambda x:(x[0], -x[1], x[2]))
        res = [[lst[0][2]]]
        for i in range(1, len(lst)):
            if lst[i][0] == lst[i-1][0]:
                res[-1].append(lst[i][2])
            else:
                res.append([lst[i][2]])
        return res

1457. 二叉树中的伪回文路径

leetcode链接
在这里插入图片描述

  • DFS记录路径+回文判断;
class Solution:
    def pseudoPalindromicPaths (self, root: TreeNode) -> int:
        # 类型: 1)返回int; 2)全局变量res:统计数目;  3)重新定义; 4)先序遍历; 
        def isPath(path):
            # 判断回文路径;
            cnt = 0
            dst = {}
            for pth in path:
                if pth not in dst:
                    dst[pth] = 0
                dst[pth] += 1
            for key, value in dst.items():
                if value%2 != 0:
                    cnt += 1
                if cnt > 1:
                    return False
            return True
        
        def dfs(root):
            # 框架1: root做点什么?
            if not root:
                return None
            # 终止条件; 
            path.append(root.val)
            if not root.left and not root.right:
                if isPath(path):
                    self.res += 1

            # 框架2:左右子树; 
            dfs(root.left)
            dfs(root.right)
            # 框架3/4
            path.pop()

        if not root:
            return 0
        path = []
        self.res = 0
        dfs(root)
        return self.res

1372. 二叉树中的最长交错路径

leetcode链接
在这里插入图片描述

  • 不会-直接抄答案了;
class Solution:
    def longestZigZag(self, root: TreeNode) -> int:
        # 思路: 对于每个节点维护, 根节点到当前节点为止的 交错路径;
        # 类型: 1) 返回int; 2)全局变量; 3)重新定义; 3)先序遍历;
        def dfs(root, left, right):
            # root: 当前节点; 
            # left:当前节点作为左路径最长; 
            # rihgt:当前节点作为右路径最长;

            # 框架1: root做点什么?
            if not root:
                return None
            # 结果判断; 
            self.res = max(self.res, left, right)

            # 框架2: 左右子树;
            dfs(root.left, right+1, 0)
            dfs(root.right, 0, left+1)

            # 框架3/4
        if not root:
            return -1
        self.res = 0
        dfs(root, 0, 0)
        return self.res

字节搜索面试:最大路径总和

通过对以上问题的总结,对于二叉树的路径问题有个 屡试不爽 的框架,所有题目都可由该框架求得。可先浏览一下该框架,后面习题代码再对比一下。基本上题解就是填这个框架。

class Solution(object):
    def maxPath(self, root, sum)def dfs(head,path,target):
            '''
            dfs传入的head必须为非None
            dfs由三部分组成 :            
            1、满足要求的处理
            2、左右子树的处理             
            3、返回结果
            '''
            if not head.right and not head.left and ###:
                # 满足要求做处理
                return 
			
            if head.left:
                # 对左子树dfs
            if head.right:
                # 对右子树dfs
                
            return ### 
        
        ####保证传入非None
        if not root:
            return ###
        ####调用dfs
        dfs(root,[head.val],root.val)
        return ###

⭐LeetCode刷题总结-树篇(中)

链接

在这里插入图片描述

二、平衡二叉树

在这里插入图片描述

  • Offer 55 - II. 平衡二叉树
110.平衡二叉树-简单

leetcode链接

在这里插入图片描述

在这里插入图片描述

  • 自己写代码时候问题:
class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        
        # 思路: 递归: 判断左右子树高度; 
        # 类型: 1) 返回bool还是高度呢?   2) 自身调用还是重写?  3)不用全局变量
        def isBTree(root, high)
            if not root:
                return [True, 0]
            
            left = isBTree(root.left, high)
            right = isBTree(root.right, high)

            return 
  • 自顶向下:从上到下,每一个节点进行高度计算: 时间复杂度: O(NlogN)
class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        # 类型: 1) bool;  2)全局变量;  3)重新定义;  4) 后序遍历; 
        # 思路: 定义去全局变量: flag, 递归判断每个节点左右子树的高度; 
        self.res = True
        def dfs(root):
            # 计算当前节点的最大深度; 
            # 框架1: root做点什么? 
            if not root:
                return 0
            # 框架2: 左右子树;
            left = dfs(root.left)
            right = dfs(root.right)
            # 框架3: 后序遍历结果处理; 左右树高高度不大于1; 
            if abs(left-right) > 1:
                self.res = False
            return max(left, right) + 1
        dfs(root)
        return self.res

三、满二叉树

894.所有可能的满二叉树-中等

leetcode链接
在这里插入图片描述

  • 直接抄的,大概率不会考,遇到就抄:
class Solution:
    memo = {1: [TreeNode(0)]}

    def allPossibleFBT(self, N: int) -> List[TreeNode]:
        if N in self.memo:
            return self.memo[N]

        res = []
        for i in range(1, N, 2):
            for left in self.allPossibleFBT(i):
                for right in self.allPossibleFBT(N - i - 1):
                    root = TreeNode(0)
                    root.left = left
                    root.right = right
                    res.append(root)
        self.memo[N] = res
        return self.memo[N]

四、完全二叉树

在这里插入图片描述

222.完全二叉树的节点个数-中等(考察统计节点个数)

leetcode链接
在这里插入图片描述

  • 二分查找思路1:利用满二叉树,肯定有完全二叉树,进行剪枝;
class Solution:
    def countNodes(self, root: TreeNode) -> int:
        # 思路:利用完全二叉树节点数目计算对于 暴力遍历进行剪枝; 
        # 类型: 1) 返回int; 2)无全局变量; 3)自身调用; 4)后序遍历; 
        # 框架1: root做点什么? 
        if not root:
            return 0
        left_height = 0
        left_node = root

        right_height = 0
        right_node = root

        # 统计左右子树高度;
        while left_node:
            left_node = left_node.left
            left_height += 1
        while right_node:
            right_node = right_node.right
            right_height += 1

        if left_height == right_height:
            return pow(2, left_height) - 1

        # 框架2: 左右子树; 
        left = self.countNodes(root.left)
        right = self.countNodes(root.right)
        # 框架3/4
        return 1 + left + right
958.二叉树的完全性检验-中等(考察检测是否为完全二叉树)

leetcode链接

在这里插入图片描述

在这里插入图片描述

  • 思路:层次遍历: 当当前节点不为空,而前一节点为空时,不是完全二叉树。
class Solution:
    def isCompleteTree(self, root: TreeNode) -> bool:
        # 思路: 当 当前节点不为空, 而前一节点为空时, 不是完全二叉树; 
        if not root: 
            return False
        queue = [root]
        # pre: 记录前一个节点; 
        pre = root

        # 队列模拟
        while queue:
            curNode = queue.pop(0)
            # 结束条件判断; 
            if not pre and curNode:
                return False
            if curNode: 
                queue.append(curNode.left)
                queue.append(curNode.right)
            pre = curNode
        return True

五、线段树

六、字典树

在这里插入图片描述

208.实现Trie(前缀树)-中等(考察创建字典树)

leetcode链接

在这里插入图片描述

  • 通过字典构建:一个单词的结束标注有多种,本文: 如果单词最后一次字符的 字典value中含有# ,则算结束;
class Trie:
    def __init__(self):
        self.lookup = {}
        
    def insert(self, word: str) -> None:
        tree = self.lookup
        for a in word:
            if a not in tree:
                tree[a] = {}
            # 最核心一步; 
            tree = tree[a]
        # 单词结束标志
        tree["#"] = "#"
        print(self.lookup)
        
    def search(self, word: str) -> bool:
        tree = self.lookup
        for a in word:
            if a not in tree:
                return False
            # 最核心一步; 
            tree = tree[a]
        # 单词结束标志; 
        if "#" in tree:
            return True
        return False
        
    def startsWith(self, prefix: str) -> bool:
        tree = self.lookup
        for a in prefix:
            if a not in tree:
                return False
            # 最核心一步; 
            tree = tree[a]
        return True
212.单词搜索II-困难(考察单词搜索)

leetcode链接
在这里插入图片描述

在这里插入图片描述

  • Trie树实现:
class Solution:
    def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
        # 字典树构造; 将word进行组织, 加速搜索: 原来 m*n次搜索, 
        # 每次遍历所有word, 现在word搜索都在一起组织,不用处理; 
        trie = {}
        for word in words:
            node = trie
            
            for char in word:
                # 1) 新加; 2) 延续
                if char not in node:
                    node[char] = {}
                node = node[char]
            node['#'] = True
        
        # 遍历方向
        directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]

        # (i,j)当前坐标;  node:当前trie节点;  
        # pre:trie树已经访问字符串;  visited: board已经访问坐标; 
        def search(i, j, node, pre, visited):
            # 网格DFS找到Trie树中叶子节点; 
            if '#' in node:
                res.append(pre)
            # 没找到,沿着4方向搜索; 
            for (di, dj) in directions:
                new_i, new_j = i+di, j+dj

                # 当前搜索节点可满足性判断; 
                # 1) 不越界; 2)board中字符在trie树; 3)当前DFS路径未访问; 
                if 0<=new_i<h and 0<=new_j<w and board[new_i][new_j] in node and (new_i, new_j) not in visited:
                    # visited是回溯过程; 不是全局过程; 
                    # 参数中调整, 模拟了回溯过程, visited也是回溯; 
                    search(new_i, new_j, node[board[new_i][new_j]], pre+board[new_i][new_j], visited|{(new_i, new_j)})
        
        res = []
        h, w = len(board), len(board[0])
        for i in range(h):
            for j in range(w):
                if board[i][j] in trie:
                    search(i, j, trie[board[i][j]], board[i][j], {(i,j)})
        return list(set(res))

七、树状数组

⭐LeetCode刷题总结-树篇(下)

链接
在这里插入图片描述

一、新概念定义问题

117.填充每个节点的下一个右侧节点指针II-中等

297.二叉树的序列化与反序列化-困难

leetcode链接
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 上图用: 队列pop(0) 和 append演示过程;

  • BFS思路:

class Codec:
    def serialize(self, root):
        """Encodes a tree to a single string.
        :type root: TreeNode
        :rtype: str
        """
        if not root: 
            return []
        q = []
        q.append(root)
        res = ''
        while q:
            node = q.pop(0)
            if node != None:
                res += str(node.val) + ','
                q.append(node.left)
                q.append(node.right)
            else:
                res += 'X,'
        return res     

    def deserialize(self, data):
        """Decodes your encoded data to tree.
        :type data: str
        :rtype: TreeNode
        """
        if not data: 
            return None
        data = data.split(',')
        root = TreeNode(data.pop(0))
        q = [root]
        while q:
            node = q.pop(0)
            if data:
                val = data.pop(0)
                if val != 'X':
                    node.left = TreeNode(val)
                    q.append(node.left)
            if data:
                val = data.pop(0)
                if val != 'X':
                    node.right = TreeNode(val)
                    q.append(node.right)
        return root

在这里插入图片描述
在这里插入图片描述

  • DFS: 序列化
class Codec:
    def serialize(self, root):
        # 先序遍历; None用#代替; 
        if not root:
            # , 至关重要; 
            return '#,'
        left = self.serialize(root.left)
        right = self.serialize(root.right)
        return str(root.val) + ',' + left + right

    def deserialize(self, data):
        # 递归构建; 
        data = data.split(',')
        print(data)
        root = self.buildTree(data)
        return root

    def buildTree(self, data):
        # 序列--> 结构化; 
        val = data.pop(0)
        if val == '#':
            return None
        root = TreeNode(val)
        # 递归序列化; 
        # 框架: 左右子树; 
        left = self.buildTree(data)
        right = self.buildTree(data)
        root.left = left
        root.right = right
        # 框架:返回当前子树; 
        return root

114.二叉树展开为链表-中等

leetcode链接
在这里插入图片描述

  • 前序遍历存储,再生成;
  • 递归思路
class Solution:
    def flatten(self, root: TreeNode) -> None:
        # 递归思路: 1) 先序遍历存储; 2) 结果生成; 
        # 递归存储; 
        preOrder = []
        def preOrderTraversal(root):
            if not root:
                return None
            preOrder.append(root)
            preOrderTraversal(root.left)
            preOrderTraversal(root.right)
        preOrderTraversal(root)

        # 生成
        size = len(preOrder)
        for i in range(1, size):
            prevNode, curNode = preOrder[i-1], preOrder[i]
            prevNode.left = None
            prevNode.right = curNode
  • 迭代思路
class Solution:
    def flatten(self, root: TreeNode) -> None:
        preOrder = []
        stack = []
        curNode = root
        while stack or curNode:
            while curNode:
                preOrder.append(curNode)
                stack.append(curNode)
                curNode = curNode.left
            curNode = stack.pop()
            curNode = curNode.right

        # 生成
        size = len(preOrder)
        for i in range(1, size):
            prevNode, curNode = preOrder[i-1], preOrder[i]
            prevNode.left = None
            prevNode.right = curNode

在这里插入图片描述

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
class Solution:
    def flatten(self, root: TreeNode) -> None:
        curNode = root
        while curNode:
            # 如果存在左节点, 进行指针转换; 
            if curNode.left:
                # pre指针: curNode的左子树的最右边叶子; 
                # nxt指针: curNode的左子树的根节点;
                preNode = nxtNode = curNode.left
                # 找到左子树中最右边叶子节点; 
                while preNode.right:
                    preNode = preNode.right
                
                # 转换:
                preNode.right = curNode.right
                curNode.left = None
                curNode.right = nxtNode
            # 转换之后,更新curNode; 
            curNode = curNode.right

998.最大二叉树II-中等-不做

834.树中距离之和-困难-不会、抄的

leetcode链接
在这里插入图片描述

在这里插入图片描述

  • 实现
class Solution(object):
    def sumOfDistancesInTree(self, N, edges):
        graph = collections.defaultdict(set)
        for u, v in edges:
            graph[u].add(v)
            graph[v].add(u)

        count = [1] * N
        ans = [0] * N
        def dfs(node = 0, parent = None):
            for child in graph[node]:
                if child != parent:
                    dfs(child, node)
                    count[node] += count[child]
                    ans[node] += ans[child] + count[child]

        def dfs2(node = 0, parent = None):
            for child in graph[node]:
                if child != parent:
                    ans[child] = ans[node] - count[child] + N - count[child]
                    dfs2(child, node)

        dfs()
        dfs2()
        return ans

二、子树问题,匹配问题

匹配类二叉树题目总结

  • 模板:
# 类型: 1)  bool类型;  2) 一般都重新定义;  3)一般不需要全局变量; 

# 主函数: 
def main(root1, root2):
	if not root1 and not root2:
		return False
	# 递归调用判断; 
	return def()  and/or  main(root1.left, root2.left / right) and/or mina()
def dfs(root1, root2):
	# root判断
	# true判断: 出现匹配完;
	if not t1 / not t2 / not t1 and not t2:
		return True
	if not ...:
		return False
	# 进一步递归子树判断; 
	return t1.val == t2.val and dfs(root1..., root2...) and dfs(root1..., root2...)  

在这里插入图片描述

508.出现次数最多的子树元素和-中等-不懂题

101-对称二叉树-简单

leetcode链接
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 思路1: 递归思路 : 深度优先遍历: 100
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        # 类型: 1) 是否型;  2)重新定义: 自身不满足;  ;  3) 递归; 

        def isMirror(root1, root2):    
            '''
            判断两个二叉树是否镜像; 
            '''
            # 框架: 
            # 步骤1: root需要做什么?  1) 满足条件判断;   2) 不满足条件判断: a:剪枝; b:退出; 
            # 步骤2:其他y不用root操心, 交给框架; 

            # root做什么? 
            # 两种写法:  True / False类型;  1)哪种情况满足; 2) 哪种情况不满足;
            
            # 两个都是空节点: 必定相等; 
            if not root1 and not root2:
                return True
            
            # 一个空,一个非空,必定不等; 
            if not root1 or not root2:
                return False

            # 交给框架:  1)  val相等;  2) 左;   3) 右; 
            return root1.val == root2.val and isMirror(root1.left, root2.right) and isMirror(root1.right, root2.left)
        
        # 可以省略; 
        '''
        # 异常判断; 
        if not root:
            return False
        '''
        return isMirror(root, root)

面试题 04.10. 检查子树-中等

leetcode链接
在这里插入图片描述

  • 模板实现:
class Solution:
    def checkSubTree(self, t1: TreeNode, t2: TreeNode) -> bool:
        # 类型: 1)返回bool; 2)无全局变量; 3)重新定义; 4)先序遍历;
        # 思路: 递归判断两个树的结构;

        def dfs(root1, root2):
            # 框架1: root做点什么? 
            if not root2:
                return True
            if not root1:
                return False
            
            # 框架2/3/4
            return root1.val == root2.val and dfs(root1.left, root2.left) and dfs(root1.right, root2.right)
        
        if not t1 or not t2:
            return False
        return dfs(t1, t2) or self.checkSubTree(t1.left, t2) or self.checkSubTree(t1.right, t2)

572. 另一个树的子树-简单

leetcode链接
在这里插入图片描述

  • 调用模板
class Solution:
    def isSubtree(self, s: TreeNode, t: TreeNode) -> bool:
        # 类型: 1) 返回bool; 2)无全局变量; 3)重新定义; 4)先序遍历;
        # 思想: 对s每个节点进行递归判断;  要求必须子树(到叶子节点,而非部分)
        def dfs(root1, root2):
            # 框架1: root做点什么?
            if not root1 and not root2:
                return True
            if not root1 or not root2:
                return False
            
            # 框架2/3/4
            return root1.val == root2.val and dfs(root1.left, root2.left) and dfs(root1.right, root2.right)
        
        if not s or not t:
            return False
        return dfs(s, t) or self.isSubtree(s.left, t) or self.isSubtree(s.right, t)

652.寻找重复的子树-中等

leetcode链接
在这里插入图片描述

  • python实现:
# 时间复杂度:
# 主要是受字符串拼接影响,假设 root.val 的长度为 a,root.left 的长度为 b,root.right 的长度为 c,
# 每次字符串拼接所需时间是 a + b + c <= N,而一共有 N 个节点,所以是 O(N^2)。
# 空间复杂度:主要是 Map 的 key 占的空间。
class Solution(object):
    def findDuplicateSubtrees(self, root):
        # 类型: 1) 返回list; 2) 全局变量; 3)重新定义; 4)先序遍历;
        count = {}
        res = []
        def dfs(root):
            # 框架1:root做点什么?
            if not root:
                return '#'
            # 框架2:左右子树
            serial = '{},{},{}'.format(root.val, dfs(root.left), dfs(root.right))

            # 框架3: 后序遍历结果处理;
            if serial not in count:
                count[serial] = 0
            count[serial] += 1
            if count[serial] == 2:
                # 注意: 返回子树根节点;
                res.append(root)
            # 框架4: 返回结果;
            return serial
        dfs(root)
        return res

865.具有所有最深结点的最小子树-中等

1110.删点成林-中等

leetcode链接
在这里插入图片描述

# 一遍DFS:如果当前结点是要被删除的,那么向上返回None,如果不需要被删除,
# 则返回原结点,上层遍历左子树和右子树后,直接赋给这个返回值。
# 还有一个优化就是把to_delete这个列表转换成集合,增加查询速度。

class Solution:
    def delNodes(self, root: TreeNode, to_delete: List[int]) -> List[TreeNode]:
        # 思路: 找到删除节点; 然后断开,分别访问;  
        # 类型: 1) 返回值;  2) 全局变量;  3)重新定义;  4)先序遍历; 
        dct = set(to_delete)
        res = []

        def dfs(root, flag):
            '''
            先序遍历; 存储结果; 
            root: 当前节点; 
            flag: 父节点是否删除标志; 
            '''

            # 框架1: root做点什么? 空节点向上返回None;
            if not root:
                return None
            
            # flag: 记录父节点是否删除; 
            deleted = root.val in dct
            # 判断以root为根节点的子树是否加入森林; 
            # 父节点要被删除, 当前节点不删除时: root作为子树根节点加入;
            if flag and not deleted:
                res.append(root)
            
            # 框架2: 左右子树; 
            left = dfs(root.left, deleted)
            right = dfs(root.right, deleted)
            root.left = left
            root.right = right

            # 框架3/4: 1) root需要删除,返回None; 2)root不需要删除,返回root; 
            return None if deleted else root

        dfs(root, True)
        return res

⭐二叉搜索树相关

  1. leetcode中常见的二叉树相关的知识点和题目总结;
  2. 面经中的二叉树题目;
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

大佬笔记-二叉搜索树操作集锦

大佬笔记-二叉搜索树操作集锦
相关题目:
leetcode-100.相同的树
leetcode-450.删除二叉搜索树中的节点
leetcode-701.二叉搜索树中的插入操作
leetcode-700.二叉搜索树中的搜索
leetcode-98.验证二叉搜索树

二叉树算法的设计的总路线:明确一个节点要做的事情,然后剩下的事抛给框架。

void traverse(TreeNode root) {
    // root 需要做什么?在这做。
    // 其他的不用 root 操心,抛给框架
    traverse(root.left);
    traverse(root.right);
}
  1. 如何把二叉树所有的节点中的值加一?
# python 
def plusOne(TreeNode root):
	if root == None:
		return None
	root.val += 1
	
	plusOne(root.left)
	plusOne(root.right)
  1. 如何判断两棵二叉树是否完全相同?
leetcode-100. 相同的树-简单

leetcode链接

100. 相同的树
给定两个二叉树,编写一个函数来检验它们是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
示例 1:
输入:       1         1
          / \       / \
         2   3     2   3

        [1,2,3],   [1,2,3]
输出: true

python题解

# python 
def isSameTree(TreeNode root1, TreeNode root2):
	# 如果都为空, 现然相同
	if root1 == None and root2 == None:
		return True
	# 一个为空, 一个非空, 显然不同
	if root1 == None or root2 == None:
		return False
	
	# 两个都非空, 但是 val不同,现然不同
	if root1.val != root2.val:
		return False

	# root1, root2 该比的都比完了
	return isSameTree(root1.left, root2.left) and isSameTree(root1.right, root2.right)

借助框架,上面这两个例子不难理解吧?如果可以理解,那么所有二叉树算法你都能解决。


二叉搜索树(Binary Search Tree,简称 BST)是一种很常用的的二叉树。它的定义是:一个二叉树中,任意节点的值要大于等于左子树所有节点的值,且要小于等于右边子树的所有节点的值。

如下就是一个符合定义的 BST:
在这里插入图片描述
下面实现 BST 的基础操作:判断 BST 的合法性、增、删、查。其中“删”和“判断合法性”略微复杂。

0-判断 BST 的合法性

leetcode-98. 验证二叉搜索树-中等

leetcode链接

98. 验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:
		节点的左子树只包含小于当前节点的数。
		节点的右子树只包含大于当前节点的数。
		所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:
    2
   / \
  1   3
输出: true

这里是有坑的, 按照刚才的思路, 每个节点自己要做的不就是比较自己和左右孩子嘛? 看起来代码:

def isVailsBST(TreeNode root):
	
	#  root 需要做什么?在这做。
	if root == None:
		return False
	# 若: 根值  <= 左子树,返回False
	if root.left and root.val <= root.left.val:
		return False 
	# 若: 根值  >= 右子树, 返回False
	if root.right and root.val >= root.right.val:
		return False
	
	# 其他的不用 root 操心,抛给框架
	return isValidBST(root.left) and isValidBST(root.right) 

!!! 但是这个算法出现了错误, BST的每个节点应该要小于 右边子树的所有节点,下面的二叉树现然不是 BST,但上述算法会误认为是。

在这里插入图片描述
出现错误,不要慌张,框架没有错,一定是某个细节问题没注意到。我们重新看一下 BST 的定义,root 需要做的,不仅仅是和左右子节点比较,而是要和左子树和右子树的所有节点比较。怎么办,鞭长莫及啊!

这种情况,我们可以使用辅助函数,增加函数参数列表,在参数中携带额外信息,请看正确的代码:
在这里插入图片描述
python 最终AC版本代码

class Solution:
    def isValidBST(self, root: TreeNode) -> bool:
        # 画图可知: 1): 右走: max不更新, min = parent;  2) 左走: max = parent, min不更新; 
        # 思路: 递归; 
        # 类型: 1) 是否型;  2) 重新定义函数;  3) 无需全局变量; 

        def isBST(root, minValue, maxValue):
            '''
            判断当前是否为 二叉搜索树; 
            root: 当前节点
            minValue: 当前子树的最小值; 
            maxValue: 当前子树的最大值; 
            '''

            # 步骤1: root做点什么? 空节点必定为二叉搜索树;  
            if not root:
                return True
            
            # 判断minValue存在性:你和刚开始根节点; 
            if minValue and root.val <= minValue.val:
                return False
            if maxValue and root.val >= maxValue.val:
                return False
            
            # 框架: 左子树
            left = isBST(root.left, minValue, root)
            right = isBST(root.right, root, maxValue)

            # 框架: 返回结果; 
            return left and right
        
        return isBST(root, None, None)

这样,root 可以对整棵左子树和右子树进行约束,根据定义,root 才真正完成了它该做的事,所以这个算法是正确的。

一、在 BST 中查找一个数是否存在

700. 二叉搜索树中的搜索-简单

leetcode链接

700. 二叉搜索树中的搜索
给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 
返回以该节点为根的子树。 如果节点不存在,则返回 NULL。

例如,
给定二叉搜索树:

        4
       / \
      2   7
     / \
    1   3
和值: 2
你应该返回如下子树:
      2     
     / \   
    1   3

根据我们的指导思想,可以这样写代码:

class Solution:
    def searchBST(self, root: TreeNode, val: int) -> TreeNode:
        # root做的事情
        if not root or not val:
            return None
        if root.val == val:
            return root
        
        left = self.searchBST(root.left, val)
        right = self.searchBST(root.right, val)
            
		if left:
			return left
		if right: 
			return right

python实现: 注意利用 BST性质进行加速搜索

class Solution:
    def searchBST(self, root: TreeNode, val: int) -> TreeNode:
        # 类型: 1) 返回值; 2) 自身递归; 3)不用全局变量; 
        
        # root做点什么? 
        if not root or not val:
            return None
        if root.val == val:
            return root

        # 框架: 左子树; 
        # BST加速
        if root.val > val:
            left = self.searchBST(root.left, val)
            return left

        # 框架: 右子树;
        # BST加速
        if root.val < val: 
            right = self.searchBST(root.right, val)
            return right

二、在 BST 中插入一个数

701. 二叉搜索树中的插入操作-中等

leetcode链接

701. 二叉搜索树中的插入操
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 
保证原始二叉搜索树中不存在新值。

注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回任意有效的结果。

例如, 
给定二叉搜索树:
        4
       / \
      2   7
     / \
    1   3
和 插入的值: 5
你可以返回这个二叉搜索树:
         4
       /   \
      2     7
     / \   /
    1   3 5
或者这个树也是有效的:
         5
       /   \
      2     7
     / \   
    1   3
         \
          4

对数据结构的操作无非 遍历 + 访问, 遍历就是‘找’, 访问就是‘改’。 具体到这个问题, 插入一个数, 就是先找到插入位置,然后进行插入操作。

上一个问题,总结了BST中的遍历框架, 就是 “找” 的问题。 直接 套上框架, 加上”改“的操作即可。 一旦涉及”改“, 函数就要返回TreeNode 类型, 并且对 递归调用的返回值进行接收。 (一定一定记住了。 )

python实现

class Solution:
    def insertIntoBST(self, root: TreeNode, val: int) -> TreeNode:
        # 思路: 数据结构操作: 遍历+访问;  找+改; 
        # 改: 返回 TreeNode类型; 

        # 框架: root做点什么? 
        if not root:
            return TreeNode(val)
        
        # 框架: 右边看看; 
        if root.val < val:
            root.right = self.insertIntoBST(root.right, val)
        # 框架:右边看看; 
        if root.val > val:
            root.left = self.insertIntoBST(root.left, val)

        return root

三、在 BST 中删除一个数

450. 删除二叉搜索树中的节点-中等

leetcode链接

450. 删除二叉搜索树中的节点
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,
并保证二叉搜索树的性质不变。
返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:

首先找到需要删除的节点;
如果找到了,删除它。
说明: 要求算法时间复杂度为 O(h),h 为树的高度。

示例:
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], 如下图所示。
    5
   / \
  4   6
 /     \
2       7
另一个正确答案是 [5,2,6,null,4,null,7]。

    5
   / \
  2   6
   \   \
    4   7

python 初级版本实现

class Solution:
    def getMin(self, root):
        # 获取当前子树中的最小值; 
        while root.left:
            root = root.left
        return root.left

    def deleteNode(self, root: TreeNode, key: int) -> TreeNode:
        '''
        re: TreeNode
        '''
        # 类型: 1) 返回值; 2) 自身递归; 3) 不用全局变量; 
        

        # 框架: root做什么? 
        if not root:
            return None

        # 框架: root.val == val
        if root.val == key:
            # 找到要删除节点, 然后对于BST进行调整; 
            # 情况1:左空,右返回; 
            if not root.left:
                return root.right
            # 情况2:右空, 左返回; 
            if not root.right:
                return root.left
            
            # 情况3:左右都非空; 
            # 处理: 1) 左边最大值填上去;  2) 右边最小值填上去; 
            minValue = self.getMin(root.right)
            # 通过修改node值实现交换,实际中不会这种操作; 
            root.val = minValue
            # 再去右子树中删除minValue节点: 
            self.deleteNode(root.right, minValue)

        # 框架: root.val > val
        elif root.val > key:
            self.deleteNode(root.left, key)
        # 框架:root.val < val
        elif root.val < key:
            self.deleteNode(root.right, key)
        
        # 框架: 返回值; 
        return root

删除操作就完成了。 注意一下,这个删除操作并不完美,因为我们一般不会通过 root.val = minNode.val 修改节点内部的值来交换节点, 而是通过一些列略微复杂的链表操作交换 root 和 minNode 两个节点。 因为具体的应用中, val 域可能会很大,修改起来耗时, 而链表操作无非改一下指针,而不会去碰内部数据。

但这里忽略这个细节, 旨在突出BST基本操作的共性,以及借助框架逐层细化问题的思维方式

四、二叉树搜索树 其他题目

下面2题: 二叉树之不同的二叉搜索树

96. 不同的二叉搜索树-中等

leetcode链接
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • python代码- 有点不像动态规划了
class Solution:
    def numTrees(self, n: int) -> int:
        if n<=1:
            return 1
        
        store = [1, 1]
        for i in range(2, n+1):
            s = i-1
            count = 0
            for j in range(i):
                count += store[j]*store[s-j]
            store.append(count)
            print(i, count)
            
        return store[n]
  • 动态规划:转移方程有点奇怪
class Solution:
    def numTrees(self, n: int) -> int:
        if n<=1:
            return 1
        
        # dp[i] 代表什么含义呢? 
        # 以i 为结尾的
        dp = [0 for _ in range(n+1)]
        dp[0], dp[1] = 1, 1
        for i in range(2, n+1):
            for j in range(i):
                dp[i] += dp[j]*dp[i-j-1]
        return dp[n]
95. 不同的二叉搜索树 II-中等-未做

leetcode链接
在这里插入图片描述

  • 本题要求输出所有的: 二叉搜索数, 肯定不能像之前那样通过规律计算数字了;
  • 回溯 + 记忆化(剪枝) 方法: 保存所有的可能结果;
class Solution:
    def generateTrees(self, n: int) -> List[TreeNode]:
        # 需要存储所有的二叉搜索树, 所以必须使用 回溯/递归 等保留结果,
        # 而不是简单递推数字; 
        # 回溯+记忆化剪枝; 
        if n == 0:
            return []
        # 记忆化递归思路: 记忆化: memo[(i, j)]: i~j 构成的所有二叉搜索树; 
        # 记忆化词典: 避免重复计算; 如: (left, right)= (2, 3)可能计算多次; 
        memo = {}
        # 递归函数: left~right范围的二叉搜索树的组织形式; 
        def left_right_BST(left, right):
            # 返回 list形式; 
            if left > right:
                return [None]

            # 记忆化查询; 
            if (left, right) in memo:
                return memo[(left, right)]
            
            # 当前范围:left~right,所有结果存储; 
            # 注意: 函数内部变量, 仅仅参与当前left~right情况下的结果; 
            ret = []
            # 2~3搜索二叉树组织形式, 取值: [2, 4)
            for i in range(left, right+1): 
                # 节点i为根节点的左右子树分别计算; 
                left_res = left_right_BST(left, i-1)
                right_res = left_right_BST(i+1, right)

                # 左右结果进行组合; 
                for l_bst in left_res:
                    for r_bst in right_res:
                        # 对于当前节点构建左右子树, 生成树; 
                        tmpTree = TreeNode(i)
                        tmpTree.left = l_bst
                        tmpTree.right = r_bst

                        ret.append(tmpTree)
            
            # 记忆化存储; 
            memo[(left, right)] = ret
            return ret

        return left_right_BST(1, n)
  • 动态规划思路: 自底向上推导-不会
99.恢复二叉搜索树-困难

leetcode链接
在这里插入图片描述
在这里插入图片描述

  • 空间复杂度: O(N), 可以进一步优化,但是不必了;
class Solution:
    def recoverTree(self, root: TreeNode) -> None:
        """
        Do not return anything, modify root in-place instead.
        """
        # 中序遍历二叉树, 并将遍历结果保存到list中; 
        if not root:
            return []
        inOreder = []
        def dfs(root):
            if not root:
                return []
            dfs(root.left)
            inOreder.append(root)
            dfs(root.right)
        dfs(root)

        # 找到: 位置出错的2个节点; 
        preNode = None
        postNode = None
        pre = inOreder[0]
        # 扫描遍历中序遍历结果,找到两个异常值; 
        for i in range(1, len(inOreder)):
            if pre.val > inOreder[i].val:
                # BST_inOrder后面的值: 偏小; 
                postNode = inOreder[i]
                if not preNode:
                    # BST_inOrder前面的值:偏大; 
                    preNode = pre
                    
            pre = inOreder[i]

        # 如果preNode 和 postNode 不为空, 则交换这两个节点, 恢复BST;
        if preNode and postNode:
            preNode.val, postNode.val = postNode.val, preNode.val
108. 将有序数组转换为二叉搜索树-简单

leetcode链接
在这里插入图片描述

  • 递归实现: 代码很像二叉树重构、其实也算是重构;
class Solution:
    def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
        # 思路: 递归;  使用二分: 将中点(考虑了奇、偶数)作root节点; 
        # 有序数组=BSD中序遍历结果; 不能确定一棵树; 
        # 平衡二叉树: h(left) ~ h(right) 差值在1以内; 
        # 类型: 1) 返回值;  2) 重新定义函数;  3) 不用全局变量; 
        def dfs(left, right):
            '''
            nums[left]~nums[right]组建BST; 
            '''
            if left > right:
                return None
            
            # 选择中间位置左边的数字作为根节点; 
            mid = (left + right) // 2

            root = TreeNode(nums[mid])
            # 框架:左右子树; 
            left = dfs(left, mid-1)
            right = dfs(mid+1, right)
            root.left = left
            root.right = right

            # 向上返回当前子树; 
            return root
        
        return dfs(0, len(nums)-1)
Offer 36. 二叉搜索树与双向链表-中等

leetcode链接
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 复杂度分析:
  • 时间复杂度 O(N): N 为二叉树的节点数,中序遍历需要访问所有节点。
  • 空间复杂度 O(N): 最差情况下,即树退化为链表时,递归深度达到 N,系统使用 O(N) 栈空间。
  • 排序:中序遍历; 循环双向链表:
class Solution:
    def treeToDoublyList(self, root: 'Node') -> 'Node':
        # 思路: 中序遍历: 增加left指针; 
        # 类型: 1) 返回值; 2)重新定义; 3)不虚全局变量; 
        def dfs(curNode):
            '''
            中序遍历结果+left\right指针;
            '''
            if not curNode:
                return 
            # 框架: 左子树: 找到底; 
            dfs(curNode.left)

            # 修改节点引用; 
            if self.preNode:
                self.preNode.right, curNode.left = curNode, self.preNode
            # 记录头节点; 
            else:
                self.head = curNode
            # 保存curNode;
            self.preNode = curNode
    
            # 框架: 右子树; 
            dfs(curNode.right)

        if not root:
            return None
        self.preNode = None
        self.head = None
        dfs(root)
        self.head.left, self.preNode.right = self.preNode, self.head

        return self.head
Offer 54. 二叉搜索树的第k大节点-简单

leetcode链接
在这里插入图片描述

  • 中序遍历
class Solution:
    def kthLargest(self, root: TreeNode, k: int) -> int:
        # 思路: 中序遍历取topK? 
        res = []
        def inOrder(root):
            if not root:
                return None
            inOrder(root.left)
            res.append(root.val)
            inOrder(root.right)
        inOrder(root)
        return res[-k]

五、最后总结

通过这篇文章,你学会了如下几个技巧:

  1. 二叉树算法设计的总路线:把当前节点要做的事情做好,其他的交给框架,不用当前节点操心。
  2. 如果当前节点会对下面的子节点有整体影响,可以通过辅助函数增长参数列表,借助参数传递信息。
  3. 在二叉树框架之上,扩展出一套BST遍历框架:
def BST(TreeNode root, int target):
	# 当前节点该做的事情
	if root.val == target:
		# 找到目标, 做点什么: 
	elif root.val < target:
		BST(root.right, target)
	elif root.val > target:
		BST(root.left, target)

4.掌握BST的基本操作: 增、删、改、查。

二叉树题目补充

  1. 翻转二叉树,难度 Easy

  2. 填充二叉树节点的右侧指针,难度 Medium

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值