Day19 二叉树:235.二叉搜索树的最近公共祖先 701.二叉搜索树中的插入操作 450.删除二叉搜索树中的节点

101 阅读5分钟

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

难度指数:😀😐

19.01.png

Q:此时节点5是不是最近公共祖先?

A:如果 从节点5继续向左遍历,那么将错过成为q的祖先, 如果从节点5继续向右遍历则错过成为p的祖先。

所以当我们从上向下去递归遍历,第一次遇到 cur节点是数值在[p, q]区间中,那么cur就是 p和q的最近公共祖先。

理解这一点,本题就很好解了。

递归解法:

代码思路:

3️⃣ 单层递归的逻辑:

在这道题,不用涉及到前中后序,因为二叉搜索树本身就是有序的,而且不需要对中间节点进行处理;

只需要有一个和一个就可以了,在哪里无所谓。

 TreeNode* traversal(TreeNode* cur, TreeNode* p, TreeNode* q) {
     if (cur == NULL) {  //刚传进来的根节点为空
         return NULL;
     }
     //左
     if (cur->val > p->val && cur->val > q->val) {  //当前节点cur比p和q大,说明应该向左子树去搜索
         TreeNode* left = traversal(cur->left, p, q);
         if (left != NULL) {  //left不为空
             return left;  //就找到了最近的公共祖先
         }
     }
     //右
     if (cur->val < p->val && cur->val < q->val) {
         TreeNode* right = traversal(cur->right, p, q);
         if (right != NULL) {
             return right;
         }
     }
     return cur;
 }

AC代码: (核心代码模式)

 /**
  * Definition for a binary tree node.
  * struct TreeNode {
  *     int val;
  *     TreeNode *left;
  *     TreeNode *right;
  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
  * };
  */
 ​
 class Solution {
 public:
     TreeNode* lowestCommonAncestor(TreeNode* cur, TreeNode* p, TreeNode* q) {
         if (cur == NULL) {
             return NULL;
         }
         //左
         if (cur->val > p->val && cur->val > q->val) {
             TreeNode* left = lowestCommonAncestor(cur->left, p, q);
             if (left != NULL) {  //若left不为空
                 return left;  //就找到了最近的公共祖先
             }
         }
         //右
         if (cur->val < p->val && cur->val < q->val) {
             TreeNode* right = lowestCommonAncestor(cur->right, p, q);
             if (right != NULL) {
                 return right;
             }
         }
         return cur;
     }
 };

迭代解法:

这道题的迭代法也很简单,就因为它是一棵二叉搜索树。

在二叉搜索树中,它把我们搜索的方向确定了,这就造成了本题的代码很简单。

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

难度指数:😀😐😕

在二叉搜索树中插入任何一个节点,都可以在叶子节点找到它的位置。

有人会问:为什么就只在叶子节点处插入?为什么不往中间其他位置插入呢?

这样改变二叉树的结构就做复杂了。

因此,要想新插入节点,我们只需考虑在叶子节点处插入就可以了。

代码思路:

1️⃣ 确定递归函数的参数和返回值:

  • 传入的参数:这棵二叉树的根节点、我们要插入的数数值val
  • 返回值:插入新的节点后,新的二叉搜索树的根节点

2️⃣ 终止条件:在本题中,遇到终止条件的时候,就找到了要插入节点的位置。

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

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

难度指数:😀😐😕😖

本题删除节点比上一题插入节点要难很多

删除节点之后要保证这棵二叉树依然是二叉搜索树,也就意味着不可避免地会改变这棵二叉搜索树的结构。

在二叉搜索树中删除节点,有以下5种情况

1️⃣ 没找到要删除的点

(下面这些情况都是可以找到要删除的点)

2️⃣ 要删的点是叶子节点(左为空,右为空) (最好删的,因为无需改变二叉搜索树的结构)

3️⃣ 要删的节点,左不空,右为空;让其父节点直接指向其左孩子

4️⃣ 要删的节点,左为空,右不空;让其父节点直接指向其右孩子

5️⃣ 要删的节点,左不空,右不空;(最复杂,因为需要大幅调整二叉搜索树的结构)

第五种情况有点难以理解,看下面动画:

19.02.gif

7 的左、右孩子,谁去继位都可以,策略都是相似的,这里是让 7 的右孩子继位。

(让左子树继位是什么样子?建议画图理解,不要眼高手低)

这道题删除复杂,代码也有点复杂

代码思路:

确定递归函数的返回值和参数:

传入这棵二叉树的根节点 root ,要删的节点 key ;删除节点后,返回新的二叉树的根节点

终止条件:

本题的终止条件相对比较复杂。

因为在这棵二叉搜索树中,找到目标节点就要进行删除操作,删除节点的操作就在终止条件里。

第 5️⃣ 步:要删掉节点 7 ,应该找到节点 7 右子树中最左侧的值,因为最左侧的值是仅次于 7 大的数

 TreeNode* delete(root, key) {
     //终止条件  (比较复杂)
     if (root == NULL) {  //没有找到要删除的节点
         return NULL;
     }
     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 {  //左不空,右不空   (最复杂)
             cur = root->right;
             while (cur->left != NULL) {
                 cur = cur->left;
             }
             cur->left = root->left;
             return root->right;
         }
     }
     
     //单层递归的逻辑
     if (key < root->val) {
         root->left = delete(root->left, key);
     }
     if (key > root->val) {
         root->right = delete(root->right, key);
     }
     return root;
 }

严格来说,这里删除的节点都是从二叉树中移除了,这里并没有写在内存中真正释放(C++需要写)。

AC代码: (核心代码模式)

 /**
  * Definition for a binary tree node.
  * struct TreeNode {
  *     int val;
  *     TreeNode *left;
  *     TreeNode *right;
  *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
  *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
  *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
  * };
  */
 class Solution {
 public:
     TreeNode* deleteNode(TreeNode* root, int key) {
         //终止条件 (比较复杂)
         if(root == NULL) {  //没有找到要删除的节点
             return root;
         }
         if (root->val == key) {  //找到要删除的节点
             if (root->left == NULL && root->right == NULL) {  //左为空,右为空
             delete root;
             return NULL;
             }
             else if (root->right == NULL) {
                 auto retNode = root->left;
                 delete root;
                 return retNode;
             }
             else if (root->left == NULL) {
                 auto retNode = root->right;
                 delete root;
                 return retNode;
             }
             else {  //左不空,右不空  (比较复杂)
                 TreeNode* cur = root->right;
                 while (cur->left != NULL) {
                     cur = cur->left;
                 }
                 cur->left = root->left;
                 TreeNode* tmp = root;
                 root = root->right;
                 delete tmp;
                 return root;
             }
         }
         //单层递归的逻辑
         if (key < root->val) {
             root->left = deleteNode(root->left, key);
         }
         if (key > root->val) {
             root->right = deleteNode(root->right, key);
         }
         return root;
     }
 };