第六章 二叉树part08
今日内容:
● 235. 二叉搜索树的最近公共祖先
● 701.二叉搜索树中的插入操作
● 450.删除二叉搜索树中的节点
详细布置
235. 二叉搜索树的最近公共祖先
相对于 二叉树的最近公共祖先 本题就简单一些了,因为 可以利用二叉搜索树的特性。
题目链接/文章讲解:代码随想录
视频讲解:https://www.bilibili.com/video/BV1Zt4y1F7ww
【链接】(文章,视频,题目)
【第一想法与实现(困难)】
-
如何理由 二叉搜索树的有序性,也就是中序遍历(左中右)有序数组?其实本题连中序遍历都不需要,而是只需要利用二叉搜索树的定义:左子树都小于(等于),右子树都大于(等于),左右都是二叉搜索树
-
又因为题设条件pq都存在,结点值不重复,就不会取上述等号
【看后想法】
-
保证cur是公共祖先。不妨设置p < q,则只需要从上往下搜索,求第一个cur结点 使得cur 在 [p, q]的左闭右闭区间中。
-
为什么第一个满足[p, q]的左闭右闭区间中的cur就是最近的公共祖先。如果再往下搜索,已知p <= cur <= q,说明pq分别属于cur的两个不同子树。left < cur < right(不重复),则向左不满足是右子树结点q的祖先,向右不满足是左子树结点p的祖先。
-
小结:只需要找第一个cur in [p, q]左闭右闭即可,当自身是祖先也满足
【实现困难】
【自写代码】
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
// 只需要找第一个cur in [p, q]左闭右闭即可,当自身是祖先也满足
if (!root) {
return nullptr;
}
if (root->val < p->val && root->val < q->val) {
// p,q在右边
return lowestCommonAncestor(root->right, p, q);
}
if (root->val > p->val && root->val > q->val) {
// p,q在左边
return lowestCommonAncestor(root->left, p, q);
}
// root正是[p, q]左闭右闭之中
return root;
}
};
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
// 迭代方法
if (!root) {
return nullptr;
}
TreeNode* cur = root;
while (cur) {
if (cur->val < p->val && cur->val < q->val) {
// p,q在右边
cur = cur->right;
} else if (cur->val > p->val && cur->val > q->val) {
// p,q在左边
cur = cur->left;
} else {
// 找到了所求的cur in [p, q]左闭右闭,min(p,q) <= cur <= max(p,q)
return cur;
}
}
// 循环结束没找到
return cur;
}
};
【收获与时长】40m
701.二叉搜索树中的插入操作
本题比想象中的简单,大家可以先自己想一想应该怎么做,然后看视频讲解,就发现 本题为什么比较简单了。
题目链接/文章讲解:代码随想录
视频讲解:https://www.bilibili.com/video/BV1Et4y1c78Y
【链接】(文章,视频,题目)
【第一想法与实现(困难)】
-
题目描述,可以不必重构搜索二叉搜索树
-
直接插入成叶子结点。记录插入位置及其双亲结点
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
// 直接插入成叶子结点。记录插入位置及其双亲结点
if (!root) {
return new TreeNode(val);
}
TreeNode* cur = root;
TreeNode* pre = nullptr;
while (cur) {
if (val < cur->val) {
pre = cur;
cur = cur->left;
} else {
pre = cur;
cur = cur->right;
}
}
// 构造pre指向cur的指向关系,cur为待插入结点位置,pre为双亲结点
cur = new TreeNode(val);
if (val < pre->val) {
pre->left = cur;
} else {
pre->right = cur;
}
return root;
}
};
【看后想法】
- 要注意处理空树的特殊情况,直接返回一个新节点
【实现困难】
【自写代码】
- 递归方法,直接更新左右结点
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
// 递归方法,直接更新左右结点
// 1递归返回值和参数:复用
// 2递归结束条件:空节点
// 3递归单层逻辑:判断左插入还是右插入,直接更新左或右结点(要么返回结点自身,要么从空节点更新成新插入的结点)
if (!root) {
return new TreeNode(val);
}
if (val < root->val) {
root->left = insertIntoBST(root->left, val);
} else {
root->right = insertIntoBST(root->right, val);
}
return root;
}
};
-
通过递归函数的返回值完成父子节点的赋值是可以带来便利的
-
递归方法,使用空返回值,需要记录双亲结点
class Solution {
// 递归方法,使用空返回值,需要记录双亲结点
private:
TreeNode* parent = nullptr;
// 1递归参数返回值,空返回值
void Traverse(TreeNode* root, int val) {
// 2递归结束条件,空节点
if (!root) {
TreeNode* node = new TreeNode(val);
if (val < parent->val) {
parent->left = node;
} else {
parent->right = node;
}
return;
}
// 3递归单层逻辑,判断插入到左还是右子树
parent = root;
if (val < root->val) {
Traverse(root->left, val);
} else {
Traverse(root->right, val);
}
}
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (!root) {
// 空树
return new TreeNode(val);
}
Traverse(root, val);
return root;
}
};
【收获与时长】30m
450.删除二叉搜索树中的节点
相对于 插入操作,本题就有难度了,涉及到改树的结构
题目链接/文章讲解:代码随想录
视频讲解:https://www.bilibili.com/video/BV1tP41177us
【链接】(文章,视频,题目)
【第一想法与实现(困难)】想不出来,直接看题解
【看后想法】
-
首先找到待删除结点,然后删除。利用二叉搜索树的性质,二叉搜索地查找,容易想到递归
-
递归三步
-
1返回值参数,节点指针作为返回值给上层调用接住
-
2递归终止条件,分类讨论:
-
1没找到,返回空
-
2(以下都是找到了)没有左右孩子,删除自己返回空
-
3只有左孩子,删除自己返回左孩子
-
4只有右孩子,删除自己返回右孩子
-
5有左右孩子,保留其中右子树,利用二叉搜索树性质,将左子树放到适合的位置,也就是右子树的最小位置(递归左孩子,最左孩子)的左孩子。删除自己返回保留的右孩子。(注意这里保留右子树也可以的,需要一点修改)
-
-
3单层递归逻辑,二叉搜索,目标值小则递归删除左孩子,目标值大则递归删除右孩子
-
【实现困难】
【自写代码】
递归方法
class Solution {
public:
// 首先找到待删除结点,然后删除。利用二叉搜索树的性质,二叉搜索地查找,容易想到递归
TreeNode* deleteNode(TreeNode* root, int key) {
// 递归三部曲
// 1返回值参数,节点指针作为返回值给上层调用接住
// 2递归终止条件,分类讨论
// 2.1没找到,返回空
if (!root) {
return nullptr;
}
// (以下都是找到了)
if (key == root->val) {
// 2.2没有左右孩子,删除自己返回空
if (!root->left && !root->right) {
delete root;
return nullptr;
}
// 2.3只有左孩子,删除自己返回左孩子
if (root->left && !root->right) {
TreeNode* res = root->left;
delete root;
return res;
}
// 2.4只有右孩子,删除自己返回右孩子
if (!root->left && root->right) {
TreeNode* res = root->right;
delete root;
return res;
}
// 2.5有左右孩子,保留其中右子树,利用二叉搜索树性质,将左子树放到适合的位置,也就是右子树的最小位置(递归左孩子,最左孩子)的左孩子。删除自己返回保留的右孩子。(注意这里保留右子树也可以的,需要一点修改)
TreeNode* cur = root->right;
while (cur->left) {
cur = cur->left;
}
cur->left = root->left;
TreeNode* res = root->right;
delete root;
return res;
}
// 3单层递归逻辑,二叉搜索,目标值小则递归删除左孩子,目标值大则递归删除右孩子
if (key < root->val) {
root->left = deleteNode(root->left, key);
}
if (key > root->val) {
root->right = deleteNode(root->right, key);
}
return root;
}
};
迭代方法
class Solution {
// 迭代方法,需要记录待删除结点的双亲
private:
TreeNode* DeleteOneNode(TreeNode* cur) {
if (!cur) {
return nullptr;
}
if (!cur->left && !cur->right) {
delete cur;
return nullptr;
}
if (cur->left && !cur->right) {
TreeNode* res = cur->left;
delete cur;
return res;
}
if (!cur->left && cur->right) {
TreeNode* res = cur->right;
delete cur;
return res;
}
TreeNode* left_most_of_right = cur->right;
while (left_most_of_right->left) {
left_most_of_right = left_most_of_right->left;
}
left_most_of_right->left = cur->left;
TreeNode* res = cur->right;
delete cur;
return res;
}
public:
TreeNode* deleteNode(TreeNode* root, int key) {
// 保证至少有一个根节点非空
if (!root) {
return nullptr;
}
TreeNode* cur = root;
TreeNode* parent = nullptr;
while (cur) {
if (key == cur->val) {
break;
}
parent = cur;
if (key < cur->val) {
cur = cur->left;
} else {
cur = cur->right;
}
}
// 只有根节点,并且就是待删除结点
if (!parent) {
return DeleteOneNode(root);
}
// 找到了,分类讨论
if (parent->left && parent->left == cur) {
parent->left = DeleteOneNode(cur);
}
if (parent->right && parent->right == cur) {
parent->right = DeleteOneNode(cur);
}
return root;
}
};
【收获与时长】1h