701. 二叉搜索树中的插入操作
解题思路:
-
1. 模拟 ,如果 根节点为空 ,就用 插入值创建根节点 直接返回。否则, cur 从 根节点 开始,比较 当前节点的值和插入值的大小关系 :
-
1)如果 插入值 < cur ,就 向左移动cur ,
-
2) 如果 插入值 > cur ,就 向右移动cur 。
-
每次 移动前 判断:
-
1) 如果 插入值 < cur且cur左子树为空 ,就使用 插入值构造节点 挂到cur的左子树 上,返回 根节点 ,
-
2) 如果 插入值 > cur且cur右子树为空 ,就 使用 插入值构造节点 挂到cur的右子树 上,返回 根节点 。
解题思路:
-
2. DFS ,递归终止: 如果 根节点为空 ,就用 插入值创建根节点 直接返回。 否则,
-
1) 如果 插入值小于当前根节点的值 ,就 递归调用左子树 ,将 返回结果(已经插入好的左子树) 挂到 当前根节点的左子树 上,
-
2) 如果 插入值大于当前根节点的值 ,就 递归调用右子树 ,将 返回结果 (已经插入好的右子树) 挂到 当前根节点的右子树 上。
-
最后返回 根节点
99. 恢复二叉搜索树
解题思路:
-
1. 中序遍历 二叉搜索树,得到 有序数组 节点, 在有序数组中找到两个错误节点,然后交互两个错误节点的值 。
-
在整个有序数组中 逆序 可能出现 2次 或 1次 ,所以在找 逆序节点 时, 每次出现逆序时都记录后一个节点,而只在第一次出现逆序时记录前一个节点 ,这样不管出现1次还是2次逆序都能正确找到。
解题思路:
-
2. 中序遍历 ,在 遍历过程中找到两个错误点 ,不用输出有序数组。中序访问根节点时,记住上一次的根节点,当前节点跟上一次的比较,是否出现逆序。
注意:这题题目要求不能改变树的结构,因此隐含的意思就是要找出被错误交换的两个节点,再将节点值交换回来。
剑指 Offer 33. 二叉搜索树的后序遍历序列
解题思路:
-
1. DFS , 由于nums是后序遍历的结果,因此 最后一个元素是根节点 。
-
在数组中找到 第一个大于根节点的位置m , 可划分出 左子树区间[i, m − 1] 、 右子树区间[m, j - 1] 、 根节点 j ,如果是 二叉搜索树 ,则应满足:
-
[i, m − 1] 内的所有节点 都小于根节点 , [m, j - 1] 内的所有节点 都大于根节点 ,如果 都满足 ,则 递归求解两个子区间按同样方式判断 。
-
递归终止: i ≥ j ,此时区间内 节点数量 ≤ 1 ,无需判别正确性,直接返回 true 。
后序数组中定位根节点很简单,关键是利用BST性质,因为BST中序遍历是一个有序的数组,其排列情况会形如 【左子树】< 【根节点】< 【右子树】,假设对这棵树后序遍历,得到的会是 【左子树】、【右子树】、 【根节点】,显然第一个大于根节点的位置属于右子树,前面的则属于左子树。但是找到这样的位置之后,还需要验证两个区间是否真的符合BST要求。
解题思路:
-
2. 单调递增栈 ,从 右往左访问后序数组 ,也就是 【根 右 左】 ,如果 当前节点 > 栈顶 ,就 压栈 ,说明是 往右子树遍历 的过程, value 是越来越大的,一旦出现了 value小于栈顶value 的时候,就表示要开始进入 左子树 了,但是这个 左子树 是从哪个节点开始的呢?
-
只要 栈顶还比当前节点大 ,就表示还是 右子树 ,就一直 弹栈 ,直到 栈顶小于节点 或者 栈空 ,此时 最后一个弹出的栈顶就是 左子树 的根 节点 。
-
接下来,数组继续往前遍历,之后的 左子树的每个节点,都要比子树的根要小 ,否则就不满足二叉搜索树定义。
这个代码很简单,但是理解起来不容易。 下面看一下在一棵正常的BST的后序数组上倒序执行单调递增栈的处理过程:
注意,这个时候记录最后一次弹出的根节点 5 ,可以发现 5 是以 2 为根节点的子树的父节点,它比以 2 为根节点的子树的节点值都要大,而此时以 2 为根节点的子树的节点都还排在数组的前面。一旦此时前面出现了小于 5 的节点,那么继续倒序往前遍历的话就会遇到该非法的节点,因此这就是为什么要倒序处理以及为什么要记录最后一次弹出栈顶的原因,是为了方便判断非法的情况。
如果整个遍历过程都没有出现比红色的 root 大的情况,最后就返回
true
。否则在遍历过程中出现大于
root
的情况,
就返回false
。
下面再看一棵非二叉搜索树的后序数组上单调递增栈的处理过程:
我们看到这时出现一个非法的节点 6,我们拿之前记录的最后一次弹出的根节点 5,跟它相比,就能判断和排除它。这个解法妙就妙在这里。
综合来看,本题方法 2 单调递增栈的解法虽然比较精妙,但是却比较难理解,一般很难想的到。相对来说,还是方法 1 寻找第一个大于根节点的位置然后划分区间进行 DFS 递归的想法更加容易理解。
108. 将有序数组转换为二叉搜索树
解题思路:
-
DFS 仿中序遍历 ,BST的 中序遍历 是 升序 的,因此 本题等同于根据中序遍历恢复二叉搜索树 。定义递归函数 dfs(nums, L, R) 返回 nums[L..R] 上构造BST的 根节点 。
-
因为本题要求 高度平衡 ,因此需要 每次递归都选择 L..R 的 中间元素创建根节点 , 然后分别到数组 [L, mid - 1] 和 [mid + 1, R] 中, 递归的构造左子树和右子树 (分治思想)将 递归结果 分别挂到 根节点的左右孩子 上&#