二叉树
1. 遍历1.3 层序遍历2. 结构转化+序列化3. 删除or构造二叉树4. 二叉搜索树4. LCA 最近公共祖先5. 深度相关6.信息传递6.1自上而下(回溯)6.2自下而上7. 树转图8. 其他二叉树速通
1. 遍历
除了正规的前序,中序,后序外,层序,中序的花样最多!
中序遍历在二叉搜索树中用的最多。
此外,要通过中序遍历非递归代码,深刻理解中序遍历每一个节点的后续节点:
- 如果当前节点有右子树,那么右子树的最左节点就是当前节点的后继节点
- 无右子树,那么需要向上看:
- 如果当前节点是父节点的左子节点,那么父节点就是当前节点的后继节点
- 如果当前节点是父节点的右子节点,那么就从当前节点一直往上走,直到走到某一个节点是其父节点的左子节点,那么其父节点是后继节点。
- 如果找不到,那就是没有后继节点。
144
145
94
426
145
1.3 层序遍历
层序遍历借助队列这个数据结构来辅助执行,层次遍历的时候,辅助队列是肯定只添加非空节点的!
不过还有别的队列不一定只添加非空节点,对空节点也进行一定的标志,比如297. 二叉树的序列化与反序列化 中,序列化的二叉树中就把空姐点也添加进去了。以及在题目101. 对称二叉树 中,也不管节点是否为空都加进去,然后再进行的比较。
此外通过DFS也可以达到层次遍历的效果,具体做法就是递归的时候,将当前层数也当作参数传进去。例如题目513. 找树左下角的值 及题目 102. 二叉树的层序遍历
相关题目:
2. 结构转化+序列化
从列表构造二叉树的题目,采用递归,套路如下:
- 先找到当前根节点值,
- 然后利用题目具体条件控制好新的列表值,进行递归构造左右子节点,
- 最后返回当前根节点。
在做构建二叉树的题目时,一般使用递归。先根据题目要求在数组中找到构建根节点的值及其下标,然后构建好根节点node。然后再递归的构建左子树右子树。最后返回node即可,注意,返回的是当前层的root
普通二叉树的序列化,N叉树的序列化,BST的序列化
114
652. 寻找重复的子树 序列化的同时查找子树
3. 删除or构造二叉树
这类构造二叉树题目,先构造好当前节点,然后node.left = 递归 node.rigth = 递归 ,然后返回当前节点。
4. 二叉搜索树
利用好中序遍历即为升序的性质,此外还要边界判断在BST中的奇效(98, )
270. 最接近的二叉搜索树值
98. 验证二叉搜索树 用中序遍历判断 + 用边界判断
4. LCA 最近公共祖先
有种通用的解法是,先用字典记下每个节点的父节点,然后类似找两链表的公共节点的思路来做。
如果查询两个节点的最近公共祖先的操作很频繁,那么可以采用空间换时间的操作:记录任意两个节点之间的最近公共祖先,方法如下:
- 对二叉树的每一颗子树(一共N颗)都进行步骤2
- 假设子树头节点为h,那么
h
所有的后代节点和h
节点的最近公共祖先都是h
节点, 且h
左子树的每个节点和h
右子树的每个节点的最近公共祖先都是h
.
整个建表的过程,时间复杂度和空间复杂度均为
O(n**2)
,之和,单次查询的复杂度为O(1)5. 深度相关
深度相关的题目其实都可以用别的方法来解决,此处列出来只是整理一下深度相关的模板顺便拓展一下思路。此外,此处总结的深度相关算法本质上是属于回溯算法的。
此类题目可能不会显性的展现出和深度相关,但是认真思考一下就会发现,采用深度策略可以解决。
比如题目 104. 二叉树的最大深度 就可以采用自底向上的方式(后序遍历)来计算深度,也可以采用自顶向下(前序遍历)的方式计算深度。此外,层序遍历的相关题目也可以采用深度来做。
深度相关代码模板(以LC.104为例)如下:
模板的核心在于递归的时候带上了当前层数,这样,每个节点就知道了自己所在层
class Solution: def maxDepth(self, root: TreeNode) -> int: if root is None: return 0 self.result = 1 self.dfs(node=root, depth=1) return self.result def dfs(self, node, depth): if node is None: return self.result = max(self.result, depth) self.dfs(node.left, depth+1) self.dfs(node.right, depth+1)
相关题目:
513. 找树左下角的值6.信息传递
走到树中的一个节点后
- 可以自上而下的传递给左节点,右节点(通过传函数参数)
- 也可以自下而上的传给父节点(通过函数返回值)
涉及深度的题目,既可以自上而下也可以自下而上
6.1自上而下(回溯)
backtracking可以理解为我们想去做一个搜索的操作,类似dfs我们在走一条路不通后我们undo我们的操作,去下一条路再次尝试,在进行的过程中其实我们使用了很多次的recursion
注意这个过程中每条我们尝试的道路是互相不影响的,这也是我们为什么说信息是自上而下传递的,比如subset这道题,找出所有的组合subset,我们的组合是top-down的
对于路径的问题,可以利用栈or队列,在遍历二叉树的时候,顺便把 从根节点到当前节点的路径 存放的队列 or 栈中。这样每次取出一个节点,顺带也可以取出 从根节点到当前节点的路径。此外亦可采用深度优先搜索(回溯算法)进行统计所有路径
信息是从父节点传下来的,所以一般是先序遍历
6.2自下而上
由要解决的problem的定义出发,尝试划分成相同problem的子问题,并且利用子问题的结果来解决原来的问题,而最关键的点有两处:base case和induction rule。
base case代表的是最小号的不可分的问题,这里指的是叶子节点;induction rule表示的是如何利用子问题的结果,表示在当前node处理leftChild,rightChild返回来的结果
比如我们在找subtree all nodes sum的时候,我们通过left,right node return back他们的sum,这样我们在每一层的current node节点就可以拿到汇总起来的当前subtree的数据
信息是return回去的,所以一般是后序遍历,当然也有中序遍历,即每一各节点只关注其左子树的信息
Tips:在做这类的题目时,想一下,递归的时候,站在每一个节点上,从该节点向左走,可以拿到左子树的信息回来,向右走可以拿到右子树的信息回来。最后带着左右子树的信息,看你怎么处理。当然,这是后序遍历。如果先带着左子树的信息回来直接处理,处理完才去右子树,那么就是中序遍历。
《程序员代码面试指南》找二叉树中的最大搜索二叉树 / 找满足二叉搜索条件的最大拓扑结构
7. 树转图
先借助字典 将二叉树转为无向图,然后在无向图上进行 DFS 或者 BFS
二叉树转无向图,代码如下:
# 版本1 初始parent为None,child为root def buildGraph(self, parent, child): # 先建立 parent 和 child 的相互映射 if parent: self.graph.setdefault(parent.val,[]).append(child.val) self.graph.setdefault(child.val,[]).append(parent.val) # 再去递归建立子节点 if child.left: self.buildGraph(child, child.left) if child.right: self.buildGraph(child, child.right) # 版本2 def buildGraph(self, root): # 先建立 parent 和 child 的相互映射 if root.left: self.graph.setdefault(root.val,[]).append(root.left.val) self.graph.setdefault(root.left.val,[]).append(root.val) self.buildGraph(root.left) if root.right: self.graph.setdefault(root.val,[]).append(root.right.val) self.graph.setdefault(root.right.val,[]).append(root.val) self.buildGraph(root.right)
相关题目:
8. 其他
《程序员代码面试指南》P.143 判断t1树中是否包含t2树的全部的拓扑结构
《程序员代码面试指南》P.144 判断t1树中是否有与t2树拓扑结构完全相同的子树
二叉树速通
二叉树完整笔记参见 二叉树 。掌握了递归之后,就可以应对95%的题目了(废话)