一、题目描述
给你二叉搜索树的根节点
root
,同时给定最小边界low
和最大边界high
。通过修剪二叉搜索树,使得所有节点的值在[low, high]
中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。
实例
二、解题思路
修剪二叉树的意思就是,将不在给定范围内的结点删除,并继续保持二叉搜索树。
注意,删除之后还要保持二叉搜索树,这非常非常重要,是这个题的难点和核心。
第一个问题:如果遇到一个结点我们要删除,那这个结点可能出现在哪里?
很简单:要么删除的
是叶子结点
,要么删除的不是叶子结点
。
1.先来讨论删除的是叶子结点的情况:
首先看这个树,要保留[3, 6] 之间的内容,我们要删除的就是2, 7结点了,那具体怎么处理呢?
只要将2
, 7
这两个结点换成NULL
就行了。
2.再来讨论删除的是非叶子结点的情况:
这里删除的是这棵树[1, 4]以外的结点,如图,要删除的就是0和4这两个结点。
对于删除结点4前面讨论过,将4替换成NULL就行了。
那0结点显然是不能用NULL直接来代替的,那应该怎么操作呢?
首先,对于0这个结点,是小于[1, 4]的low的,所以,如果0这个结点被删除了,就不是一个合格的二叉搜索树了。
应该怎么做呢?
就应该被它右子树中找一个在[1,4]内的子树,用这个子树来代替这个几点。
具体怎么操作呢?
然后对0的右子树进行递归检查,目的是检查0的右子树中的结点是否都是在[1, 4]范围内的,如果,0的右子树都是在[1, 4]内的,说明我们可以直接用0的右子树来代替0,修剪就完成了。
一、递归检查以2为根的子树,首先检查2,发现是在[1, 4]内的
二、递归检查以1为根的子树,首先检查1,发先是在[1, 4]内的
三、以2为根的子树检查完毕,即0的左子树都是在[1, 4]内的
所以,用0的右子树来代替0就可以了。
同样的道理,如果不是这个树,是别的树,对这个树的每一个结点/子树都可以做如下判断
- 判断这个结点和[1, 4]的大小
- 如果小于1,就在这个结点的右子树中找到一个合格的树来代替这个结点
- 如果大于4,就在这个结点的左子树中找到一个合格的树来代替这个结点
三、代码解析
二叉树递归算法的逻辑和代码结合比较难理解。一定要结合代码和例子,自己手动推导下具体例子在代码中运行的流程。代码下面还有例子。
TreeNode* trimBST(TreeNode* root, int low, int height){
//如果检测到了NULL,也就是都检测到叶子结点的孩子了,或者这个树是空树,就返回NULL
if (root == NULL) return NULL;
//判断当前检测的这个结点是否小于low
if (root->val < low) {
//如果这个结点的val小于low,就要去这个结点的右子树去找,
//找一个合格的子树来代替当前结点
TreeNode* right = trimBST(root->right, low, height);
return right;//如何让右子树代替当前结点呢,就是将递归找到的那个盒子子树根返回
//这行代码要和下面*代码共同理解
}
//判断当前检测的这个结点是否大于height(逻辑和root->val < low 类似)
if (root->val > height) {
TreeNode* left = trimBST(root->left, low, height);
return left;
}
//如果这个结点在[low, height]范围内,那就递归去这个结点的左子树去寻找合规的子树
//并将这个修剪过的子树返复制个当前结点的左子树,当然,如果这个结点所有左子树都合规
//那么,这个结点的左子树也会被原封不动的返回过来
root->left = trimBST(root->left, low, height);
//以咱们上面例子距离,根节点的左子树最后的返回值就是修改后的子树
root->right = trimBST(root->right, low, height);//和上面同理
return root;
}
这里还是以这个树为例子,将例子结合代码手推导。
- 首先检查3是否是
NULL
- 检查3是否小于
low
- 检查3是否大于
height
- 给3的左子树赋值为左子树修递归剪过后的子树
- 首先检查0是否是
NULL
- 检查0是否小于
low
- 去0的右子树找递归修剪过的子树
- 检查2是否为
NULL
- 检查2是否小于
low
- 检查2是否大于
height
- 给2的左子树赋值为左子树递归修剪过后的子树
- 首先检查1是否为
NULL
- 检查1是否小于
low
- 检查1是否大于
height
- 给1的左值树赋值为左子树递归修剪过后的子树
- 检查1的左子树为
NULL
- 返回NULL
- 1的左子树赋值为返回的
NULL
- 返回1为根的子树
- 检查1的左子树为
- 2的左子树赋值为返回的1
- 返回2为根的子树
- 首先检查1是否为
- 检查2是否为
- 返回0修剪过的左子树
- 首先检查0是否是
- 3的左子树赋值0修剪过的左子树
- 3的右子树赋值为右子树递归修剪过后的子树(右子树逻辑简单这里就不展开)
- 返回3为根的树
- 结束
注意,所有黄色高亮都是本层递归的返回,每一次缩进代表一层递归。