day20
235. 二叉搜索树的最近公共祖先 - 力扣(LeetCode)
class Solution {
public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {int x = root->val;if(p->val < x && x > q->val){ // 递归左子树return lowestCommonAncestor(root->left, p, q);}if(p->val > x && q->val > x){return lowestCommonAncestor(root->right, p, q);}return root;}
};
701. 二叉搜索树中的插入操作 - 力扣(LeetCode)
class Solution {
public:TreeNode* insertIntoBST(TreeNode* root, int val) {if(root == nullptr) return new TreeNode(val);if(root->val > val){root->left = insertIntoBST(root->left, val);}else{root->right = insertIntoBST(root->right, val);}return root;}
};
先记住 BST 的规则
-
左子树所有值 < 根节点值
-
右子树所有值 > 根节点值
(这题保证没有重复;若允许重复,我们统一规定“相等走右边”也行。)
这个函数的“承诺”(很关键)
insertIntoBST(root, val)
承诺:
把
val
正确插到以root
为根的这棵子树里,并返回“插入后这棵子树的根指针”。
有了这个承诺,下面每行就特别好理解了。
每一行在干嘛
① if (!root) return new TreeNode(val);
-
如果当前这棵子树根本就不存在(指针是
nullptr
),说明我们走到了一个空位——正是新节点该放的位置。 -
于是创建新节点并把它的指针返回上去。
这就是递归的终止条件(base case)。
② root->left = insertIntoBST(root->left, val);
-
如果
val
比当前root->val
小,它应该在左子树里。 -
我们把“在左子树里插入”的任务递给左孩子:
insertIntoBST(root->left, val)
。 -
这一步会返回“插完之后的左子树根指针”。我们再把它接回到
root->left
上。
这样做的意义:万一左子树本来是空(nullptr
),递归会创建一个新节点并返回它的指针,我们接回去,就把新节点挂成了root
的左孩子。
③ root->right = insertIntoBST(root->right, val);
-
同理,若
val
大(或相等时统一走右边),就把任务丢给右子树,并把“插完后的右子树根指针”接回root->right
。
④ return root;
-
别忘了我们函数的承诺:返回“插入后这棵子树的根”。
-
对于当前这一层来说,根就是
root
本人(我们可能改动的是它的左/右孩子指针),所以把root
原样返回即可。 -
最顶层返回的就是整棵树的根;这点对“原本空树”的情况尤其重要:如果
root
一开始是nullptr
,第一行会新建节点并返回,新根就被正确传回给调用者了。
走一遍例子(最能帮你建立直觉)
假设当前树是:
7/ \3 9
插入 5
:
-
调用
insert(7, 5)
:5 < 7,去左子树
→root->left = insert(3, 5)
-
调用
insert(3, 5)
:5 > 3,去右子树
→root->right = insert(nullptr, 5)
(因为 3 的右孩子本来是空) -
调用
insert(nullptr, 5)
:命中空位,return new Node(5)
-
回到第二层:把返回的指针接回
3->right
,然后return 3
-
回到第一层:把返回的指针接回
7->left
(其实还是原来的 3,只是它多了个右孩子 5),然后return 7
结果树:
7/ \3 9\5
你看到“接回去”的意义了吧:谁递归下去,谁负责把子树接回来。
复杂度 & 栈深
-
时间:
O(h)
(h
是树高;平衡时约log n
,退化成链时是n
)。 -
额外空间:递归调用栈
O(h)
(迭代写法可做到O(1)
)。
常见疑问速答
-
为什么要写
root->left = …
/root->right = …
?
因为递归可能在空位创建一个新节点并把它的指针返回来,你必须把这个指针挂回到父节点对应的孩子指针上,这样树才更新到最新形态。 -
为什么最后
return root
就行?
当前这一层的“子树根”就是root
本人;我们只可能改了它的孩子指针,但根没变,所以返回它就满足“承诺”。 -
如果原树是空的呢?
顶层root
是nullptr
,第一行会new
一个节点并直接返回;调用者拿到的就是新根。
450. 删除二叉搜索树中的节点 - 力扣(LeetCode)
class Solution {
public:TreeNode* deleteNode(TreeNode* root, int key) {if(!root) return nullptr; // if(key < root->val){ // 递归左子树root->left = deleteNode(root->left, key);} else if(key > root->val){root->right = deleteNode(root->right, key);} else{ // 找到要删除的节点if(root->left == nullptr){return root->right;}if(root->right == nullptr) return root->left;if(root->left && root->right){TreeNode* node = root->left;while(node->right){node = node->right;}root->val = node->val;root->left = deleteNode(root->left,node->val);}}return root;}
};