持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第31天,点击查看活动详情
题目链接:669. 修剪二叉搜索树
题目描述
给你二叉搜索树的根节点 root ,同时给定最小边界 low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在 [low, high] 中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。
提示:
- 树中节点数在范围 内
- 树中每个节点的值都是 唯一 的
- 题目数据保证输入是一棵有效的二叉搜索树
示例 1:
输入: root = [1,0,2], low = 1, high = 2
输出: [1,null,2]
示例 2:
输入: root = [3,0,4,null,2,null,null,1], low = 1, high = 3
输出: [3,2,null,1]
整理题意
题目给定一颗二叉搜索树的根节点 root,同时给定一个区间 [low, high],要求对这颗二叉搜索树进行修剪,使得修剪后的二叉搜索中所有节点值都在给定的区间范围内,并且要求修剪后的二叉搜索保留在原树中的元素相对结构。返回修剪后的二叉搜索的根节点。
解题思路分析
需要注意题目给定的是一颗 二叉搜索树,其次是闭区间 [low, high]。
二叉搜索的性质:
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉搜索树。
方法一:递归
由于给定的是一颗 二叉搜索树,它的左、右子树也分别为 二叉搜索树,所以修剪当前这颗二叉搜索树也就修剪它的左子树和右子树,那么我们可以通过递归的方式进行修剪。
方法二:迭代
通过代码迭代循环的方式来优化递归函数所使用的栈空间。
迭代的好处在于节省了递归所用的栈空间,优化了空间复杂度。
具体实现
方法一:递归
- 如果当前节点为空,返回
nullptr(作为递归的边界进行返回) - 如果当前节点不为空且值不在给定区间范围内,那么需要将其进行修剪:
- 如果当前节点小于给定区间范围
low,那么返回其修剪后的右子树节点; - 如果当前节点大于给定区间范围
high,那么返回其修剪后的左子树节点;
- 如果当前节点小于给定区间范围
- 如果当前节点不为空且在给定区间范围内,那么对其左子树和右子树进行递归修剪即可;
- 返回修剪后的根节点。
方法二:迭代
- 将根节点移动至给定的
[low, high]区间内; - 判断是否存在这样的根节点,如果不存在返回
nullptr; - 通过迭代进行修剪左、右子树;
- 返回修剪后的根节点。
复杂度分析
- 时间复杂度:递归和迭代 的时间复杂度都为 ,其中 为二叉树的结点数目。
- 空间复杂度:递归 的空间复杂度为 。递归栈最坏情况下需要 的空间。迭代 的空间复杂度为,仅需常数空间。
代码实现
方法一:递归
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
if(root == nullptr) return nullptr;
if(root->val < low) return trimBST(root->right, low, high);
if(root->val > high) return trimBST(root->left, low, high);
root->left = trimBST(root->left, low, high);
root->right = trimBST(root->right, low, high);
return root;
}
};
方法二:迭代
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
// 移动根节点至区间 [low, high] 内(注意条件括号)
while(root && (root->val < low || root->val > high)){
if(root->val < low) root = root->right;
else root = root->left;
}
// 不存着区间内的节点返回 nullptr
if(root == nullptr) return nullptr;
// 修剪左子树
TreeNode *now = root;
while(now->left){
if(now->left->val < low) now->left = now->left->right;
else now = now->left;
}
// 修剪右子树
now = root;
while(now->right){
if(now->right->val > high) now->right = now->right->left;
else now = now->right;
}
return root;
}
};
注意判断条件需要打括号,否则根据运算符优先级会导致条件判断出错。
总结
- 需要注意该题给定的二叉树是一颗 二叉搜索树,所以在修剪的时候可以直接修剪掉不在区间范围内的节点以及该节点的左子树或右子树;
- 递归的方法代码更为简洁,迭代的方法在空间复杂度上更优。
- 递归的方法需要注意判断递归终止边界。
- 测试结果:
根据测试结果可以看出,二者在时间复杂度上一致,迭代的空间复杂度更优。(由于测试用例较少,所以差距不明显)
结束语
很多人喜欢拿“顺其自然,随遇而安”来安慰自己,为人生中的不如意开脱。殊不知,真正的顺其自然是竭尽所能之后的不强求,而非两手一摊的不作为。努力地去改变,你就能有新收获。新的一天,加油!