二叉树1️⃣
刷二叉树
重要性就💝
很多经典的算法,都是树的相关问题,点击这里有说明为什么。
回归到树的问题,也就是回归到这几行代码之上。
1 | /* 二叉树遍历框架 */ |
再说说经典算法 快速排序 归并排序,这其中,快速排序就是个二叉树的前序遍历,归并排序就是个二叉树的后序遍历。
快速排序的逻辑:
快速排序的逻辑是,若要对 nums[lo..hi]
进行排序,我们先找一个分界点 p
,通过交换元素使得 nums[lo..p-1]
都小于等于 nums[p]
,且 nums[p+1..hi]
都大于 nums[p]
,然后递归地去 nums[lo..p-1]
和 nums[p+1..hi]
中寻找新的分界点,最后整个数组就被排序了。
1 | void sort(int[] nums, int lo, int hi) { |
归并排序逻辑:
若要对 nums[lo..hi]
进行排序,我们先对 nums[lo..mid]
排序,再对 nums[mid+1..hi]
排序,最后把这两个有序的子数组合并,整个数组就排好序了。
1 | void sort(int[] nums, int lo, int hi) { |
秘诀
写递归算法的关键是要明确函数的「定义」是什么,然后相信这个定义,利用这个定义推导最终结果,绝不要跳入递归的细节,脑袋里的栈根本就想不明白。
写树相关的算法,简单说就是,先搞清楚当前 root
节点该做什么,然后根据函数定义递归调用子节点,递归调用会让孩子节点做相同的事情。
相关练习
反转二叉树
相当于二叉树的前序遍历
1 | class Solution { |
填充每一个节点的下一个右侧节点指针
输入:root = [1,2,3,4,5,6,7]
输出:[1,#,2,3,#,4,5,6,7,#]
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化的输出按层序遍历排列,同一层节点由 next 指针连接,’#’ 标志着每一层的结束。
二叉树的问题难点在于,如何和将题目的要求细化到每个节点都需要做的事情。
解决方法就是添加辅助函数
1 | class Solution { |
将二叉树展开成链表
其实就是一个后序遍历,先把左右节点拉直,在进行处理。
1 | class Solution { |
根据中序后序遍历构造二叉树
根据中序遍历和后序遍历的特点,我们可以清楚地知道,中序是左根右,后序是左右根,所以根据此我们可以得到根节点,只要找到中心序遍历中节点的位置,再判断出左右子树的大小,进行递归,就解决啦。
1 | class Solution { |
这样看性能不是很好时间复杂度o(),空间复杂度o(),我们可以利用哈希表来进行根节点的定位,避免每一次递归中循环产生的额外时间。
1 | class Solution { |
寻找重复子树
1 | class Solution { |
时间复杂度 O(N^2) 遍历二叉树节点 N,序列化时间N
空间复杂度 O(N^2) map 的大小
优化:
1 | ``` |
1 |
|
1 |
|
1 |
|
但是仔细想想,这种方法的时间复杂度在最坏情况下去考虑是 o(N),N 是 BST 节点的个数,但是红黑树之类的增删改查都是 o(logN) 的复杂度,所以我们来想想怎么改进。
来看看 o(logN) 的复杂度是怎样实现的?答案还是 BST 的定义,左子树小,右子树大,所以每个节点可以去对比自身的值来决定去哪边搜索。
举个例子,我想查找排名 k 的元素,而当前节点知道自己排第 m ,我们来比较 m 和 k 的大小。
m==k
找到了,返回当前节点k<m
去左子树搜索第 k 个元素k>m
去右子树搜索第 k-m-1 个元素
怎么实现节点知道自己的排第几呢,需要在 TreeNode
中额外添加属性 int size;
,记录以自己 为根的二叉树有多少个节点。
二叉搜索树转化累加树
根据题意去思考一下,思路很简单
利用了 BST 的特性,中序遍历有序,然后我们将中序遍历进行改造使他能够降序排列,然后取出值进行取和,然后把值替换,就可以了。
1 | class Solution { |
二叉搜索树的增删改以及验证合法性
删除二叉搜索树中的节点
按照框架进行编写(注意修改链表的递归一定要进行赋值操作,否则不生效)
主函数意义:
删除这个树结构中所要查找的值。
分析细节:
- key 是叶子结点
- key 是结点但是有单边树
- key 是结点且有双边树
- 此时我们的策略就是寻找当前后继结点或者前继结点,让最接近他的数值作为新的结点
- 然后再进行替换与删除
1 | class Solution { |
验证二叉搜索树
就是要当前节点的左子树全比自己小,右子树全比自己大,左右子树都是二叉搜索树。
注意哦!是左子树的所有节点的值都比 root 小,所以我们要对每一轮的验证中加入神定下来的铁律,就是增加限制最大值最小值的约束
1 | class Solution { |
通过添加辅助函数的方法增加约束,这是二叉搜索树的常用及技巧!!
二叉搜索树中的搜索
充分利用左小右大的特点!很简单
1 | class Solution { |
二叉搜索树中的插入
插入自然而然的涉及到谁做根的问题,所以这种时候有不同的解法
第一种:常规算法
1 | class Solution { |
框架
所以对于 BST 的增删改查是有框架的
1 | void BST(TreeNode root, int target) { |
除此之外,如果当前节点会对下面的子节点有整体影响,可以通过辅助函数增长参数列表,借助参数传递信息。
验证 BST 合法性
不同的二叉搜索树①
题目相关描述自行点击 ☝ 链接
核心思想就是穷举,但是如何具体到每一个节点之上呢?
我们来看 [1,2,3,4,5] 这个数组,单看 3 这个节点,左右子树分别是 [1,2],[4,5]。我们求出这个数组对应的 BST 数量,再进行相乘,就是 3 这个节点所对应的 BST 的数量,我们递归运算即可。每次递归出这个范围内的二叉树的数量。
但是递归之后计算时间复杂度发现,有太多重复运算的地方了,所以我们要加入一个备忘录,是什么样的数据结构来存储呢?
因为要存储的数据是 [1,2],[1,3],[4,5],[3,5],[2,4] 这样的数据,所以来说二维数组是一个不错的选择!
1 | class Solution { |
不同的二叉搜索树②
题目相关描述自行点击 ☝ 链接
与上一题不同的是,本道题返回的是树节点的集合。但是大体思路都是一样的。
唯一不同之处在于,此题需要两层 for 循环去构造节点,而不能像刚才那样直接相乘。
1 | class Solution { |
- 本文链接:http://example.com/2021/05/19/%E4%BA%8C%E5%8F%89%E6%A0%91%E2%91%A0/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。
GitHub IssuesGitHub Discussions