0%

二叉搜索树

删除节点:
LC669 修剪二叉搜索树
LC450 删除二叉搜索树中的节点

根据有序数组生成BST:
LC108 有序数组生成BBST[2]
LC109 有序链表生成BBST
LC95 生成所有BST[2](注意在空区间时,要返回vector<TreeNode*>{nullptr}
LC96 生成所有BST - 数量[2]
LC654 生成最大的BST[1]

验证BST
LC98 验证BST[2]这个题会用INT32_MIN来测试,所以我们用INT64_MIN来记录上一个节点。
LC面试题33 二叉搜索树的后续遍历序列
⚠️LC255 验证前序遍历序列二叉搜索树(单调栈)

二叉搜索树展成链表:
⚠️LC426 BST展开为双向链表
LC114 BST展开为单向链表

二差搜索树的后继节点:
LC173 二叉搜索树迭代器 hasNext()的条件
⚠️LC285 Inorder Successor in BST没有双亲节点指针
LC510 Inorder Successor in BST II有双亲节点指针

LC230 Kth Smallest Element in a BST
LC272 最接近的二叉搜索树值II

LC99 恢复二叉搜索树[2]没有INT32_MIN这种坑
⭕️LC538 把二叉搜索树转变为累加树[1]
⚠️LC501 二叉搜索树中的众数[1]

BST节点的删除

LC669 修建二叉搜索树

给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在[L, R]中。

  • 如果一个节点<L,则这个节点和他的左子树都将被裁掉。在他的右子树上递归调用。
  • 如果一个节点>R,则这个节点个他的右子树都将被裁掉。在他的左子树上递归调用。
  • 如果节点属于[L, R],在他的左右子树上递归调用。

LC450 删除二叉搜索树中的节点

因为没有指向父节点的指针,为了简化逻辑,必须采用递归的方式来进行。通过递归函数的返回值来重置父节点中指向孩子节点的指针。

  • 首先我们根据二叉搜索树的性质找到该节点
  • 如果该节点没有孩子,则直接返回nullptr即可。
  • 如果该节点有孩子,为了保证二叉搜索树的性质,需要将该节点的值和其【前序】/【后继】节点的值交换。交换后,在其【前序】/【后继】节点对应的子树上再递归地删除有对应值的节点。该节点最终一定会被移动到叶子节点上。

从有序数组构造二叉搜索树

排序数组转为平衡二叉搜素树

LC108 将有序数组转换为二叉搜索树

不加证明的给出以下结论:如果我们每次都选择区间的中点来作为根节点,那么构造出来的二叉树必然是平衡的。
思路:

- 选择区间中点,构造根节点
- 对左侧区间递归调用
- 对右侧区间递归调用
- 将构造的根节点,和递归调用的结果(左右子树)连接起来。
- 递归终止条件:区间为空。返回一个空指针即可。

LC109 有序链表转换二叉搜索树

解法一:遍历链表转换为可以随机访问的数组,用前一题的解法即可。


解法二:二叉搜索树的中序遍历序列有序。先计算链表长度。为保证二叉搜索树平衡,仍选择区间中点作为根节点。模拟中序遍历,在链表上不断移动head指针。

最大二叉树

LC654 最大二叉树

思路同上,只不过是将区间中点换成了区间中的最大值。

生成给定区间所有的二叉搜索树

LC95 不同的二叉搜索树II

给定一个整数 n,生成所有由 1 … n 为节点所组成的二叉搜索树。根据搜索树的性质,一个节点的左子树中的所有元素都比该节点小,一个节点的右子树中的所有元素都比该节点大。那么如果我们选1作为根节点,[2 … n]只能作为他的右子树。如果我们选择k < n作为根节点,[1 … k - 1]只能作为他的左子树,[k+1 … n]只能作为他的右子树。那么我们再在他的左右区间递归调用这个函数,就可以生成所有的二叉搜索树了。

generateTrees(first, last)

  • 递归终止条件:[first, last)区间为空,即!(first < last),这时返回一个只包含nullptr的集合
  • 选择第i个节点作为根节点
    • 生成所有可能的左子树集合:generateTrees(first, i)
    • 生成所有可能的右子树集合:generateTrees(i+1, last)
  • 遍历所有可能的左子树,右子树组合。对于每个组合
    • 生成一个当前的根节点
    • 将左右子树连接上去
    • 把这个根节点加入结果集合
  • 返回结果集合

求数量:LC96 不同的二叉搜索树

求数量如果还采用上面的递归方法会TLE。见数学类问题#卡塔兰数

验证BST

验证BST本体

LC98 Validate Binary Search Tree
思路:这个题不能简单的通过前序遍历递归判断一个节点是否 大于左孩子 && 小于右孩子。二叉搜索树要求一个节点的右子树的所有节点都比该节点大,一个节点的左孩子的所有节点都比该节点小。不是简单的和【左孩子】【右孩子】的关系。正确的思路:中序遍历,记录前值,比较大小,如果后面的数不比前面大,二叉搜索树不正确。这个题坑比较多,前值不能简单的初始化设置成INT_MIN,因为他测试用例里有INT_MIN。所以得设置个isFirstElement的flag来判断前值是否已经设置好。或者用容量更大的int64_t,省去很多流程控制语句。

优化:因为递归调用的过程总是需要一次又一次传引用,我们考虑使用循环遍历的方法。

验证BST后续遍历序列

AcWing46 二叉搜索树的后序遍历序列
LC面试题33 二叉搜索树的后续遍历序列
解法一:使用递归的分治法

回忆后序遍历的特点:

  • 根节点一定在最后
  • 遍历序列当中的数可以被根节点partition成两部分,每一部分都是一个二叉搜索树。再递归判断这两部分是否是二叉搜索树。

复杂度分析

  • 最差为$T(n) = T(n - a) + T(a) + \Theta(n) \Rightarrow O(n^2)$的时间复杂度。
  • 如果该序列为平衡二叉树生成的后序遍历序列,使用主定理,为$T(n) = 2T(n/2) + \Theta(n) \Rightarrow O(n\log {n})$的时间复杂度。
  • 空间复杂度为$O(1)$

解法二:使用循环的方法

思路:每次检查数组最后一个数字是否能将前面的数组partition,如果能,去掉最后一个数,再重新检查,直到数组长度为1。

原理:后序遍历序列中,树的根节点位于整个序列最后,原理同解法一。去掉树的根节点后,最后的节点为该二叉搜索树的右子树的根节点。该节点的值必然大于左子树中任一节点的值。考虑右子树的后序遍历序列中,前一半为比右子树根节点小的,后一半是大于等于右子树根节点的。左子树中任一节点值都比右子树根节点小,将左子树的后序遍历序列贴在右子树的后序遍历序列前面,则该序列仍然能比右子树的根节点partition。

复杂度分析
时间复杂度:$\Theta(n^2)$
空间复杂度:$O(1)$


验证BST前序遍历序列

LC255 二叉搜索树的前序遍历序列

解法1:类似上一题,不断的用数组头去检查能否partition后面的区间。或递归检查也可。

参考:
单调栈校验BST前序遍历

还是有点不太理解原因。维护一个单调栈,并要求待入栈的节点 > 从栈中出来的所有节点。

BST与链表

BST展开为双向链表

剑指Offer 36, LC426 Convert Binary Search Tree to Sorted Doubly Linked List, AcWing 49

只需要中序遍历,记录上一个节点指针,并连接即可。最后我们还需要把第一个节点和最后一个节点连接起来。第一个节点可以在第一次访问节点时记录,最后一个节点自然就是遍历完成后的”上一个指针”,将他俩连接起来即可。


BST展开成单向链表

LC114 Flatten Binary Tree to Linked List

思路:

  • 如果没有左子树,就进入右子树
  • 如果有左子树,就把右子树换成左子树。然后将右子树连接在交换后的右子树的最右节点处。
  • 不断检查,直到没有右子树

二叉搜索树的第k小节点

剑指Offer 54, LC230 Kth Smallest Element in a BST
思路就是二叉搜索树的中序遍历序列就是按顺序输出。
不推荐的办法:Morris遍历。Morris遍历不能中途退出!!,如果中途退出,会使破坏树的结构。

最接近的二叉搜索树值

LC272 最接近的二叉搜索树值II

使用一个大小为k的priority_queue,遍历所有的节点即可。

BST后继节点

LC173 二叉搜索树迭代器

提供一个二差搜索树的根节点。构建一个二差搜索树的向前迭代器。我们可以改造用stack中序遍历二差搜索树的代码。将大循环拆开。

注意这里hasNext()的判断条件不仅仅是!stack.empty()。因为当stack当中只有一个元素时,运行#1处的代码后,stack也会变空。因此hasNext()的判断条件应该是m_root || !m_stack.empty()


LC285 Inorder Successor in BST, LC285 Inorder Successor in BST II, 剑指Offer 8, AcWing19 二叉树的下一个节点

一个节点的【后继节点】是:

  • 如果这个节点的右子树非空,【后继节点】是右子树中的最小节点(右子树中一直沿着左走到头)
  • 如果这个节点的右子树是空的,【后继节点】就是第一个把该节点当作左孩子的祖先节点(有可能不存在)。

一个节点的【前序节点】是:

  • 如果这个节点的左子树非空,【前序节点】就是左子树中的最大节点(左子树中一直沿着右走到头)
  • 如果这个节点的左子树是空的,【前序节点】就是第一个把该节点当作右孩子的祖先节点(有可能不存在)。

当有parent指针的时候,如果节点右子树为空,我们很容易通过父亲节点找到他的后继。
LC285 Inorder Successor in BST II

LC285 Inorder Successor in BST

当没有parent指针的时候,我们只能靠改节点的值,从头遍历来找到该节点的后继。将当前节点称为cur,从根节点开始cur = root,初始化res = nullptr

  • 循环直到cur == nullptr
    • 如果cur < p,向左走cur = cur->left并记录备份下res = cur
    • 否则cur = cur->right
      可以自己用一个二叉搜索树试一下,会发现这段程序包括了所有的情况:
    • 右子树非空
    • 右子树为空,且是最后一个节点。
    • 右子树为空,但不是最后一个节点。

恢复二叉搜索树

LC99 恢复二叉搜索树

思路:
中序遍历二叉搜索树,记录前一个node的值,寻找中序遍历中的逆序。如果发现pre比cur大,就把pre和cur都记录到数组中。

  • 如果这两个被错误交换的节点在中序遍历序列中是紧挨着的,那么最后会找到一对这样的pre和cur [pre, cur]
  • 如果不是挨着的,那么就会找到2对这样的pre和cur。[pre1, cur1, pre2, cur2]
  • 无论找到几个,只需要把这个数组的头和尾node的值交换即可。

二叉搜索树转变为累加树

LC538 把二叉搜索树转变为累加树
要求:每个节点的值是原来的节点值加上所有大于它的节点值之和。
思路:做一个反向的中序遍历(right, root, left),不断累加节点的值即可。

二叉搜索树中的众数

LC501 二叉搜索树中的众树
中序遍历即可,所有相等的值必然集中在一起。

LC530 二叉搜索树的最小绝对差
最小绝对差一定发生在中序遍历序列中两个相邻的数之间,中序遍历即可。