450.删除二叉搜索树中的节点
不理解用tmp保存root节点,然后删除?root=root->right不会覆盖吗?
需要考虑要删除的节点是不是叶子节点,有无左右孩子
有左右孩子的话,需要将左孩子节点移动到右孩子节点的左面节点的左孩子上。
669. 修剪二叉搜索树
但是有返回值,更方便,可以通过递归函数的返回值来移除节点,大概解释下:
- 定义一个递归函数,它接受一个节点作为参数,并尝试在该节点及其子树中找到要移除的节点。
- 如果当前节点就是要移除的节点,返回该节点,并在适当的情况下更新其父节点的引用以跳过这个被移除的节点。
- 如果当前节点不是要移除的节点,递归地在左子树和右子树中查找并移除节点。
- 如果在左子树或右子树中找到了要移除的节点,更新当前节点的子节点引用以跳过被移除的节点。
迭代法是一种通过循环来重复执行某段代码直到满足某个条件为止的方法。在迭代过程中,通常使用循环结构(如while
或for
循环)来控制代码的执行次数。迭代法不需要额外的函数调用栈空间,因为它直接在当前的函数栈帧中完成所有操作。
在剪枝二叉搜索树时,迭代法可以通过维护一个指针(如cur
)来遍历树的节点,并根据需要更新指针的指向。这种方法不需要递归调用,因此可以避免额外的函数调用开销和栈空间使用。
1. 如果根节点为空,那么直接返回nullptr。 if (!root) return nullptr;
2. 移动根节点到[L, R]范围内:
while (root != nullptr && (root->val < L || root->val > R)) {
if (root->val < L) root = root->right; // 小于L往右走
else root = root->left; // 大于R往左走
}
这段代码的目的是确保root节点位于[L, R]的范围内。如果root的值小于L,那么它会移动到root的右子树中;如果root的值大于R,那么它会移动到root的左子树中。
3. 处理左子树中小于L的节点:
TreeNode *cur = root;
// 此时root已经在[L, R] 范围内,处理左孩子元素小于L的情况
while (cur != nullptr) {
while (cur->left && cur->left->val < L) {
cur->left = cur->left->right;
}
cur = cur->left;
}
从root开始,遍历左子树,并删除所有值小于L的节点。这是通过将其父节点的左指针指向其右子节点来实现的。
4. 重置cur并处理右子树中大于R的节点:
cur = root;
// 此时root已经在[L, R] 范围内,处理右孩子大于R的情况
while (cur != nullptr) {
while (cur->right && cur->right->val > R) {
cur->right = cur->right->left;
}
cur = cur->right;
}
然后,再次从root开始,遍历右子树,并删除所有值大于R的节点。这是通过将其父节点的右指针指向其左子节点来实现的。
5. 返回修剪后的根节点:
return root;
最后,返回修剪后的树的根节点。
递归逻辑
- 如果
root->val < low
:- 这种情况下,当前节点
root
及其左子树中的所有节点都应该被删除,因为它们都小于low
。 - 我们递归地调用
trimBST
来处理root
的右子树,并返回修剪后的右子树的根节点作为新的root
。
- 这种情况下,当前节点
- 如果
root->val > high
:- 这种情况下,当前节点
root
及其右子树中的所有节点都应该被删除,因为它们都大于high
。 - 我们递归地调用
trimBST
来处理root
的左子树,并返回修剪后的左子树的根节点作为新的root
。
- 这种情况下,当前节点
- 如果
low <= root->val <= high
:- 这种情况下,当前节点
root
的值在允许的范围内,所以我们需要保留它。 - 我们递归地调用
trimBST
来处理root
的左子树和右子树,并将修剪后的左子树的根节点赋值给root->left
,将修剪后的右子树的根节点赋值给root->right
。 - 最后返回
root
,因为它现在是修剪后的子树的根节点。
- 这种情况下,当前节点
关键点
- 由于BST的特性(父节点的值大于左子树中所有节点的值,小于右子树中所有节点的值),我们可以直接根据当前节点的值与
low
和high
的比较结果来决定是否保留该节点以及是否继续递归处理其子树。 - 递归是处理树形结构的有效方法,它可以将复杂的问题分解为更小的子问题来解决。在这个问题中,递归地修剪左子树和右子树是保持BST性质的关键。
- 函数的返回值是修剪后的子树的根节点,这允许我们在递归调用中构建并返回修剪后的树。
108.将有序数组转换为二叉搜索树
核心在于利用了二叉搜索树(BST)的特性以及二分查找(Binary Search)的思想来迭代地将一个有序数组转化为一个BST。
首先,让我们分析这段代码:
-
traversal
函数是一个私有辅助函数,它接受一个有序数组nums
、一个左索引left
和一个右索引right
作为参数。这三个参数定义了一个子数组的范围,该函数的任务是根据这个子数组的范围来构建BST的一个子树。 -
如果
left > right
,说明子数组为空(或只有一个元素但已经被前面的递归处理了),那么返回nullptr
,表示没有节点需要创建。 -
否则,找到子数组的中间元素
mid
,这个元素将成为当前BST子树的根节点。这利用了BST的特性:根节点的值总是大于左子树的所有值,小于右子树的所有值。 -
递归地调用
traversal
函数来构建左子树(使用left
到mid - 1
的索引范围)和右子树(使用mid + 1
到right
的索引范围)。这是二分查找的关键步骤,它将原始问题(构建整个BST)分解为两个更小的问题(构建左子树和右子树)。 -
最后,将左子树和右子树连接到根节点,并返回根节点。
-
sortedArrayToBST
函数是公开的,它接受一个有序数组nums
并调用traversal
函数来构建BST的根节点。这个根节点是整个BST的入口点。
class Solution {
private:
TreeNode* traversal(vector<int>& nums, int left, int right) {
if (left > right) return nullptr;
int mid = left + ((right - left) / 2);
TreeNode* root = new TreeNode(nums[mid]);
root->left = traversal(nums, left, mid - 1);
root->right = traversal(nums, mid + 1, right);
return root;
}
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
TreeNode* root = traversal(nums, 0, nums.size() - 1);
return root;
}
};
538.把二叉搜索树转换为累加树
将二叉搜索树(BST)转换为一个累加树(Greater Sum Tree),其中每个节点的新值是该节点原始值及其所有后续节点(在中序遍历中的顺序)原始值的和。由于BST的特性(左子节点值小于父节点,右子节点值大于父节点),我们可以通过反中序遍历(右-根-左)来实现这个转换。
在反中序遍历中,我们从BST的最大值开始(即最右边的节点),然后向上遍历到根节点,并继续向左遍历,这样我们就能确保在遍历到每个节点时,我们已经计算出了所有后续节点的和。
1. 不需要递归函数的返回值做什么操作了,要遍历整棵树。
2. 遇空就终止。
3. 于当前访问的节点cur
,我们将其值更新为cur.val + pre.val
(如果pre
是节点)或cur.val + pre
(如果pre
是整数值)。然后,我们更新pre
为当前节点cur
(如果pre
是节点)或cur.val
(如果pre
是整数值)。
class Solution {
private:
// 私有成员变量pre,用于记录前一个节点的累加值(初始化为0)
int pre = 0; // 记录前一个节点的数值
// 定义一个私有函数traversal,用于执行反中序遍历(右-根-左)并更新节点值
void traversal(TreeNode* cur) { // 右中左遍历
// 如果当前节点为空,则返回(结束递归)
if (cur == NULL) return;
// 先遍历右子树
traversal(cur->right);
// 更新当前节点的值为原值加上前一个节点的累加值
cur->val += pre;
// 更新pre为当前节点的值(包括累加值),以便后续节点使用
pre = cur->val;
// 再遍历左子树
traversal(cur->left);
}
public:
// 公开成员函数convertBST,接收一个二叉搜索树的根节点作为参数
// 执行反中序遍历并更新节点值,然后返回根节点(此时根节点已经是累加树的根节点)
TreeNode* convertBST(TreeNode* root) {
// 初始化pre为0
pre = 0;
// 执行反中序遍历并更新节点值
traversal(root);
// 返回转换后的累加树的根节点
return root;
}
};