代码随想录算法训练营第22天 | 二叉树part08:● 235. 二叉搜索树的最近公共祖先 ● 701.二叉搜索树中的插入操作 ● 450.删除二叉搜索树中的节点

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

用236 普通二叉树(没顺序的)代码也可以过,但是本题还是要利用特性:搜索二叉树有序

关键:

如果一个节点的值在 p 和 q 之间(即 p < cur < q 或者 q < cur < p),那么这个节点就是 p 和 q 的最近公共祖先。 我觉得甚至不用随想录说的第一次遇到 cur节点是数值在[p, q]区间中,即 节点5,此时可以说明 p 和 q 一定分别存在于 节点 5的左子树,和右子树中” 第一次,就是只要满足就是了。不过他的意思应该是找到就行。如果数值在pq之间就一定是最近的了,因为再远的话,就pq都在一个子树里面了。

我写的↓,我处理null确实和他gpt写的不一样 

 TreeNode* traverse(TreeNode *node, int large, int small) {
        if (node->val > small && node->val < large) return node;
        else if (node->val > large && node->left) 
            return traverse(node->left, large, small);
        else if (node->val < small && node->right) 
            return traverse(node->right, large, small);
        return node;
}

TreeNode* lowestCommonAncestor(TreeNode* node, TreeNode* p, TreeNode* q) {
    int large = std::max(p->val, q->val);
    int small = std::min(p->val, q->val);
    if(node==nullptr) return nullptr;
    return traverse(node, large, small);
}

 gpt写的:

TreeNode* traverse(TreeNode *node, int large, int small) {
    if (node == nullptr) return nullptr;

    if (node->val > small && node->val < large) {
        return node;
    } else if (node->val > large) {
        return traverse(node->left, large, small);
    } else if (node->val < small) {
        return traverse(node->right, large, small);
    } else {
        return node;
    }
}

TreeNode* lowestCommonAncestor(TreeNode* node, TreeNode* p, TreeNode* q) {
    int large = std::max(p->val, q->val);
    int small = std::min(p->val, q->val);
    return traverse(node, large, small);
}

 随想录精简版真的够精简的:

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

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

也有复杂的,要改变结构的插入,但是可以不做那个。可以不考虑题目中提示所说的改变树的结构的插入方式,只是找到符合大小的空节点就行。

自己写的递归,但我觉得写的不好,是到处都在打补丁的代码。现在真的几乎所有题都是写的递归,感觉迭代抽空也要练练了

    TreeNode* prev=nullptr;
    TreeNode * newnode=nullptr;
    void traverse(TreeNode *node, int val){
        if(node==nullptr) return;
        
        if(node->left) traverse(node->left,val);
        if(prev==nullptr && node->val>val){
            newnode= new TreeNode(val);
            node->left=newnode;
        }
        if(prev && prev->val<val && node->val >val){
            newnode= new TreeNode(val);
            if(node->left==nullptr) node->left=newnode;
            else if(prev->right==nullptr) prev->right=newnode;
        }
        prev=node;//?
        if(node->right) traverse(node->right,val);
    }

    TreeNode* insertIntoBST(TreeNode* root, int val) {
        traverse(root, val);
        if(root==nullptr) root=new TreeNode(val);

        else if(newnode==nullptr && prev && prev->val<val){
            newnode= new TreeNode(val);
            prev->right=newnode;
        }
        return root;
    }

看随想录写的,此题递归func用 void 或者 有return返回值的都可以,有return返回值的明显看起来简洁多了。根据随想录的void把我的void 递归修改一下,原来有以下几个问题:

1.再次强调二叉搜索数遍历不是左中右,要用val大小和node val判断。递归func既然一开始写了if(node==nullptr) return;,去左右孩子就不要考虑if(node->left)这么写了。

2. 我的逻辑乱七八糟的,一开始只考虑了值在prev 和node值中间的情况,后来还为小于最小的,大于最大的打补丁。 其实清晰逻辑很简单:就按照搜索二叉的顺序,大了去右边,小了去左边,那找到第一个空的地方就是该插入的。下面是随想录的递归func是void的

class Solution {
private:
    TreeNode* parent;
    void traversal(TreeNode* cur, int val) {
        if (cur == NULL) {
            TreeNode* node = new TreeNode(val);
            if (val > parent->val) parent->right = node;
            else parent->left = node;
            return;
        }
        parent = cur;
        if (cur->val > val) traversal(cur->left, val);
        if (cur->val < val) traversal(cur->right, val);
        return;
    }

public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        parent = new TreeNode(0);
        if (root == NULL) {
            root = new TreeNode(val);
        }
        traversal(root, val);
        return root;
    }
};

下面是随想录的,递归函数有返回值的

 TreeNode* insertIntoBST(TreeNode* root, int val) {
        if (root == NULL) {
            TreeNode* node = new TreeNode(val);
            return node;
        }
        if (root->val > val) root->left = insertIntoBST(root->left, val);
        if (root->val < val) root->right = insertIntoBST(root->right, val);
        return root;
    }

迭代本质是一样的,也是需要记录一个prev。随想录:

 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 删除二叉搜索树中的节点

这题比较难,但是居然自己做出来了,递归逻辑和随想录的一样,没差

但是看了随想录关于需删node左右孩子都有的情况:一开始自己想的涉及旋转什么,特别乱。看了随想录发现,其实就是:把左subtree放入右subtree最左的node的左孩子处,然后让右subtree的root当新node。想通了真的非常简单。

需要注意:1.没做内存管理,delete node,在gpt帮助下改了(其实我自己不会写

TreeNode* toDelete = node; // 保存要删除的节点的指针; 

delete toDelete; // 释放要删除的节点

2. 搜索二叉树的遍历顺序不是前中后这样算的,因为是根据 大于等于小于val这样算的,是并列的if else条件,所以顺序怎么样写都行

void traverse(TreeNode * &node,int key){
    if(node==nullptr) return;
    
    if(node->val > key && node->left) 
        traverse(node->left, key);
    else if(node->val < key && node->right) 
        traverse(node->right, key);
    else if(node->val==key){
        TreeNode* toDelete = node; // 保存要删除的节点的指针
        if(node->left==nullptr && node->right==nullptr) 
            node=nullptr;
        else if(node->left && node->right==nullptr) 
            node=node->left;
        else if(node->right && node->left==nullptr) 
            node=node->right;
        else {
            TreeNode* leftmost=node->right;
            while(leftmost->left){
                leftmost=leftmost->left;
            }
            leftmost->left=node->left;
            node=node->right;
        }
        delete toDelete; // 释放要删除的节点的内存
    }
}

TreeNode* deleteNode(TreeNode* root, int key) {
    traverse(root, key);
    return root;
}

普通二叉树的删除,和用迭代删除搜索二叉树的代码先放在这,有点不想看了,下次有空再看

迭代↓

class Solution {
private: 
    // 将目标节点(删除节点)的左子树放到 目标节点的右子树的最左面节点的左孩子位置上
    // 并返回目标节点右孩子为新的根节点
    // 是动画里模拟的过程
    TreeNode* deleteOneNode(TreeNode* target) {
        if (target == nullptr) return target;
        if (target->right == nullptr) return target->left;
        TreeNode* cur = target->right;
        while (cur->left) {
            cur = cur->left;
        }
        cur->left = target->left;
        return target->right;
    }
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (root == nullptr) return root;
        TreeNode* cur = root;
        TreeNode* pre = nullptr; // 记录cur的父节点,用来删除cur
        while (cur) {
            if (cur->val == key) break;
            pre = cur;
            if (cur->val > key) cur = cur->left;
            else cur = cur->right;
        }
        if (pre == nullptr) { // 如果搜索树只有头结点
            return deleteOneNode(cur);
        }
        // pre 要知道是删左孩子还是右孩子
        if (pre->left && pre->left->val == key) {
            pre->left = deleteOneNode(cur);
        }
        if (pre->right && pre->right->val == key) {
            pre->right = deleteOneNode(cur);
        }
        return root;
    }
};

普通二叉树删除节点

TreeNode* deleteNode(TreeNode* root, int key) {
        if (root == nullptr) return root;
        if (root->val == key) {
            if (root->right == nullptr) { // 这里第二次操作目标值:最终删除的作用
                return root->left;
            }
            TreeNode *cur = root->right;
            while (cur->left) {
                cur = cur->left;
            }
            swap(root->val, cur->val); // 这里第一次操作目标值:交换目标值其右子树最左面节点。
        }
        root->left = deleteNode(root->left, key);
        root->right = deleteNode(root->right, key);
        return root;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值