代码随想录刷题攻略--二叉树5--祖先问题与二叉搜索树的修改

例题1:二叉树的最近公共祖先

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

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

 找最近的公共祖先时,要从 p 和 q 结点出发向上找,所以我们的处理顺序是后序遍历

思路是:从某个结点出发,观察其左子树、右子树是否存在 p、q 结点,

若没有,返回NULL,继续向高度更高的结点出发,寻找其左子树、右子树是否存在 p、q 结点;

若有,返回 p 或 q 所在的结点,向上传达这个子树中含有我们要找的结点的信息,如果某个结点首次发现它的子树同时含有 p、q 结点,那么该结点就是 p、q 的最近公共祖先;

另外要注意的是,一个结点的祖先结点也可以是它自己,所以如果某结点自身就是 q 或 p,则返回自身。

1、递归函数的返回类型和参数

参数是树结点和要查找的 p、q 结点,递归函数要返回最近公共祖先,所以类型是TreeNode

2、终止条件

若当前结点是空,则返回空;若当前结点是 p、q,则返回当前结点(这里我一开始没想清楚,如果根结点就是 p, q 为其子树里的某个结点,那不就直接返回根结点了么,但其实确实是直接返回根结点,因为根结点是所有结点的祖先)。

3、单层处理逻辑

找左子树有没有 p、q,找右子树有没有 p、q,然后对这2个查找结果进行处理。

整体代码:

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == NULL) return NULL;
        if(root == p || root == q) return root;

        //后序处理
        TreeNode* le = lowestCommonAncestor(root->left,p,q);
        TreeNode* ri = lowestCommonAncestor(root->right,p,q);

        if(le == NULL && ri == NULL)return NULL;
        else if(le == NULL && ri != NULL) return ri;
        else if(le != NULL && ri == NULL) return le;
        else return root;
    }
};

例题2:二叉搜索树的最近公共祖先

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

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

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

例题3:删除二叉搜索树中的结点

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

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

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

 

删除二叉树结点最复杂的情况就是待删除的结点有左右孩子。有左右孩子,把左孩子接到右孩子的最左边,返回右孩子即可。值得注意的是,我们返回的是树指针,所以要有地方来接住返回值,代码如下:

class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if(root == NULL) return NULL;

        if(root -> val > key)
        root -> left = deleteNode(root -> left, key);//左
        if(root -> val < key)
        root -> right = deleteNode(root -> right, key);//右

        //中
        if(root -> val == key)//找到了待删除元素,处理
        {
            if(root->left==NULL && root->right==NULL){
                return NULL;
            }else if(root->left!=NULL && root->right==NULL){
                return root->left;
            }else if(root->left==NULL && root->right!=NULL){
                return root->right;
            }else{//有左右孩子,把左孩子接到右孩子的最左边,返回右孩子
                 TreeNode* cur = root->right; // 找右子树最左面的节点
                 while(cur->left)
                    cur = cur->left;
                cur->left = root -> left;
                return root->right;
            }    
        }
        return root;
    }
};

我的代码还有不完善的地方,比如我们应该删除结点,释放内存 :

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

例题4:修建二叉搜索树

给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>=L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。 

直接想法:遍历每个结点,如果不符合标准就调用例题3里的删除函数。

但我们可以不删除,只截取我们想要的部分。

如果root(当前节点)的元素小于low的数值,那么应该递归右子树,并返回右子树符合条件的头结点。

代码如下:

if (root->val < low) {
    TreeNode* right = trimBST(root->right, low, high); // 寻找符合区间[low, high]的节点
    return right;
}

如果root(当前节点)的元素大于high的,那么应该递归左子树,并返回左子树符合条件的头结点。

例题5:将有序数组转换为二叉搜索树

 

将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

示例:

 其实数组构造二叉树,构成平衡树是自然而然的事情,因为大家默认都是从数组中间位置取值作为节点元素,一般不会随机取。所以想构成不平衡的二叉树是自找麻烦。

本质就是寻找分割点,分割点作为当前节点,然后递归左区间和右区间

分割点就是数组中间位置的节点。

那么问题来了,如果数组长度为偶数,中间节点有两个,取哪一个?

取哪一个都可以,只不过构成了不同的平衡二叉搜索树,我们要保证取的方式都相同。

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值