算法学习Day22二叉树插入和删除

发布时间 2024-01-04 08:08:41作者: HQWQF

Day22二叉树插入和删除

By HQWQF 2024/01/03

笔记


235. 二叉搜索树的最近公共祖先

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5]

示例 1:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。

递归法

对于二叉搜索树来说,如果一个节点是另外两个节点的公共祖先,那这个节点的值一定在这两个节点的值之间,对于深度经可能大的要求,我们可以在第一次碰到值在这两个节点的值之间的节点就返回这个节点

递归法代码

由于我们不知道p和q的大小关系,所以我们都要判断:

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == NULL){return NULL;}
        if(p->val <= root->val&& root->val <= q->val){return root;}
        else if(q->val <= root->val&& root->val <= p->val){return root;}
        TreeNode* left = lowestCommonAncestor(root->left,p,q);
        TreeNode* right = lowestCommonAncestor(root->right,p,q);
        if(left){return left;}
        else if(right){return right;}
        return NULL;
    }
};

但是其实,我们可以利用二叉搜索树的有序性来避免没必要的递归,我们知道:若当前节点值都大于p、q的值,那目标节点一定在当前节点的左子树中,若当前节点值都小于p、q的值,那目标节点一定在当前节点的右子树中,利用这个信息去向对应的子树继续递归,如果节点值不大于也不小于p、q的值,说明就在其中。

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;
    }
};

值得注意的是,这里题目给的数据集是一定会有结果的,所以我们省去了很多null的判断。

迭代法

所以和递归法相同的思路。

值得注意的是,这里题目给的数据集是一定会有结果的,所以我们省去了null的判断直接使用了死循环。

迭代法代码

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        while(1)
        {
            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
            {
                break;
            }            
        }
        return root;
    }
};

701.二叉搜索树中的插入操作

给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。

注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果

示例 1:

输入: root = [4,2,7,1,3], val = 5输出:[4,2,7,1,3,5]

迭代法

我们其实可以不去理会改变二叉树结构情况,直接根据二叉搜索树的有序性去寻找恰当的位置,如果找到了null,就说明这个位置就是合适的位置,新建节点连上就行。

迭代法代码

class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        if (root == nullptr) {
            return new TreeNode(val);
        }
        TreeNode* cur = root;
        while (1) {
            TreeNode* tmp;
            if (cur->val < val) {
                tmp = cur->right; 
                if (tmp == nullptr) {
                    TreeNode* newNode =  new TreeNode(val);
                    cur->right = newNode;
                    return root;
                } else {
                    cur = tmp;  // 移动到右子树继续搜索
                }
            } else if (cur->val > val) {
                tmp = cur->left; 
                if (tmp == nullptr) {
                    TreeNode* newNode =  new TreeNode(val);
                    cur->left = newNode;
                    return root;
                } else {
                    cur = tmp;  // 移动到左子树继续搜索
                }
            }
        }
    }
};

可以发现我们的代码中有很多嵌套,这是因为我们需要分别处理找到null后该位置是上一个节点的左节点还是右节点的情况,但是其实我们可以在循环中不要进行新建节点插入,而是在找到合适的位置后跳出循环,然后将保存上一个节点的tmp和val对比来得出其左右关系。

class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        if (root == NULL) {
            TreeNode* node = new TreeNode(val);
            return node;
        }
        TreeNode* cur = root;
        TreeNode* parent = root; // 这个很重要,需要记录上一个节点,否则无法赋值新节点
        while (cur != NULL) {
            parent = cur;
            if (cur->val > val) cur = cur->left;
            else cur = cur->right;
        }
        TreeNode* node = new TreeNode(val);
        if (val < parent->val) parent->left = node;// 此时是用parent节点的进行赋值
        else parent->right = node;
        return root;
    }
};

递归法的思路比较类似,就不赘述了。

450.删除二叉搜索树中的节点

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。

一般来说,删除节点可分为两个步骤:

  1. 首先找到需要删除的节点;
  2. 如果找到了,删除它。

示例 1:

输入: root = [5,3,6,2,4,null,7], key = 3输出:[5,4,6,2,null,null,7]
解释: 给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。
另一个正确答案是 [5,2,6,null,4,null,7]。

递归法

我们需要分类讨论需要删除的节点的情况:

有以下五种情况:

  • 第一种情况:没找到删除的节点,遍历到空节点直接返回了
  • 找到删除的节点
    • 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
    • 第三种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
    • 第四种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
    • 第五种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。

递归法代码

class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (root == nullptr) return root; // 第一种情况:没找到删除的节点,遍历到空节点直接返回了
        if (root->val == key) {
            // 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
            if (root->left == nullptr && root->right == nullptr) {
                ///! 内存释放
                delete root;
                return nullptr;
            }
            // 第三种情况:其左孩子为空,右孩子不为空,删除节点,右孩子补位 ,返回右孩子为根节点
            else if (root->left == nullptr) {
                auto retNode = root->right;
                ///! 内存释放
                delete root;
                return retNode;
            }
            // 第四种情况:其右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
            else if (root->right == nullptr) {
                auto retNode = root->left;
                ///! 内存释放
                delete root;
                return retNode;
            }
            // 第五种情况:左右孩子节点都不为空,则将删除节点的左子树放到删除节点的右子树的最左面节点的左孩子的位置
            // 并返回删除节点右孩子为新的根节点。
            else {
                TreeNode* cur = root->right; // 找右子树最左面的节点
                while(cur->left != nullptr) {
                    cur = cur->left;
                }
                cur->left = root->left; // 把要删除的节点(root)左子树放在cur的左孩子的位置
                TreeNode* tmp = root;   // 把root节点保存一下,下面来删除
                root = root->right;     // 返回旧root的右孩子作为新root
                delete tmp;             // 释放节点内存(这里不写也可以,但C++最好手动释放一下吧)
                return root;
            }
        }
        if (root->val > key) root->left = deleteNode(root->left, key);
        if (root->val < key) root->right = deleteNode(root->right, key);
        return root;
    }
};