235. 二叉搜索树的最近公共祖先
- 思路:
- 利用二叉搜索树有序的特点,如果中间节点是 q 和 p 的公共祖先,那么 p < 中节点 < q 或者 q < 中节点 < p。相较于普通二叉树最近公共祖先的查找,不需要用使用回溯,因为二叉搜索树自带方向性,可以方便的从上向下查找目标区间,遇到目标区间内的节点,直接返回。
- 那么这个中间节点一定是最近公共祖先吗?
- 答案是 是的。假设p < 中节点 < q,若此时从中节点的左孩子向下遍历,是无法遍历到q的,说明这条路径中一定没有q的祖先,从中节点右孩子向下遍历同理。因此,遇到一个节点的左子树里有p,右子树里有q,那么当前节点就是最近公共祖先。
- 递归:若当前节点值比p、q都大,那么向左遍历;若当前节点值比p、q都小,那么向右遍历;剩下的情况,就是当前节点是最近公共祖先的情况,直接返回当前节点。
- 迭代:根据二叉搜索树的有序性,直接按照目标区间找即可。
//递归
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(!root) return NULL;
//若当前节点值比p、q都大,那么向左遍历
if(root->val > p->val && root->val > q->val) {
TreeNode* left = lowestCommonAncestor(root->left, p ,q);
if(left) return left;
}
//若当前节点值比p、q都小,那么向右遍历
if(root->val < p->val && root->val < q->val) {
TreeNode* right = lowestCommonAncestor(root->right, p ,q);
if(right) return right;
}
//剩下的情况,就是当前节点是最近公共祖先的情况,直接返回当前节点
return root;
}
};
//递归精简
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root->val > p->val && root->val > q->val) {
return lowestCommonAncestor(root->left, p, q);
} else if (root->val < p->val && root->val < q->val) {
return lowestCommonAncestor(root->right, p, q);
} else return root;
}
};
//迭代
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
while(root) {
if(root->val > p->val && root->val > q->val) {
root = root->left;//向左下继续遍历
}
else if(root->val < p->val && root->val < q->val) {
root = root->right;//向右下继续遍历
}
else return root;
}
return NULL;
}
};
701.二叉搜索树中的插入操作
- 思路:
- 本题有多种插入方式,例如待插入的val大小介于根节点与其左孩子之间,那么可以将这个val插入在根节点与其左孩子之间成为根节点的新的左孩子,也可以取代成为新的根节点,原本的根节点则成为它的右孩子,此外,也存在不改变树的原有结构的插入方式:只要按照二叉搜索树的规则去遍历,遇到空节点就插入节点,也就是全都插入成为叶子节点。
- 递归:
- 参数和返回值:参数就是根节点指针,以及要插入元素。返回值为节点类型TreeNode * ,本题有返回值的话,可以利用返回值完成新加入的节点与其父节点的赋值操作。
- 终止条件:找到遍历的节点为null的时候,就是要插入节点的位置了,并把插入的节点返回。
- 单层递归逻辑:根据插入元素的数值大小,决定递归方向,就不需要遍历整棵树。下一层将加入节点返回,本层用root->left或者root->right将其接住,这样就通过递归函数返回值完成了新加入节点的父子关系赋值操作。
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if(!root) {
TreeNode* node = new TreeNode(val);
return node;
}
if(val < root->val) root->left = insertIntoBST(root->left, val);
if(val > root->val) root->right = insertIntoBST(root->right, val);
return root;
}
};
450.删除二叉搜索树中的节点
- 思路:
- 考虑被删除节点在二叉搜索树中的位置的所有情况:
- 没有找到要删除的节点,遍历到空节点后直接返回
- 找到删除的节点:
- 待删除节点为叶子节点(左右孩子都为空),直接删除节点, 返回NULL为根节点。
- 待删除节点的左孩子为空,右孩子不为空,则右孩子顶替待删除节点的位置,返回右孩子为根节点。
- 待删除节点的右孩子为空,左孩子不为空,则左孩子顶替待删除节点的位置,返回左孩子为根节点。
- 待删除节点的左、右孩子都不为空,则将待删除节点的左子树移动到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。该情况如图所示。
- 递归三部曲:
- 参数以及返回值:通过递归返回值删除节点
- 终止条件:遇到空返回,找到要删除的节点则将新的根节点返回给上一层
- 单层递归的逻辑:把新的节点返回给上一层,上一层就要用 root->left 或者 root->right接住
- 考虑被删除节点在二叉搜索树中的位置的所有情况:
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
//终止条件,删除的操作就在终止条件里
if(!root) return nullptr;//1.没找到要删除的节点,返回空
if(root->val == key) {
//2.要删除的为叶子节点,直接删除节点,返回空
if(!root->left && !root->right) {
delete root;
return nullptr;
}
//3.要删除的结点的左孩子为空,右孩子不为空,右孩子顶替当前节点的位置并被返回
if(!root->left) {
TreeNode* newRoot = root->right;
delete root;
return newRoot;
}
//4.要删除的结点的右孩子为空,左孩子不为空,左孩子顶替当前节点的位置并被返回
else if(!root->right) {
TreeNode* newRoot = root->left;
delete root;
return newRoot;
}
//5.要删除的节点左右孩子都不为空,则将这个节点的左子树移动到右子树中的最左面节点的左孩子的位置
//并返回删除节点的右孩子为新的根节点
else {
TreeNode* cur = root->right;
while(cur->left) { //找右子树最左面的节点
cur = cur->left;
}
cur->left = root->left;//把要删除的节点(root)左子树放在cur的左孩子的位置
TreeNode* tmp = root;//保存临时节点便于后续释放
root = root->right;//返回待删除节点的右孩子作为新root
delete tmp;
return root;
}
}
//单层逻辑:把新的节点返回给上一层,上一层就要用 root->left 或者 root->right接住,我个人理解这一步就是完成了和上一层结点的连接,所以返回值必须要接住
if(root->val > key) root->left = deleteNode(root->left, key);
if(root->val < key) root->right = deleteNode(root->right, key);
return root;
}
};
总结
二叉搜索树添加节点只需要在叶子上添加就可以的,不涉及到结构的调整,而删除节点操作涉及到结构的调整