代码随想录算法训练营第二十二天|Leetcode235 二叉搜索树的最近公共祖先、Leetcode701 二叉搜索树中的插入操作、Leetcode450 删除二叉搜索树中的节点
● Leetcode235 二叉搜索树的最近公共祖先
题目链接:Leetcode235 二叉搜索树的最近公共祖先
视频讲解:代码随想录|二叉搜索树的最近公共祖先
题目描述:给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 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。
示例 2:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
说明:
·所有节点的值都是唯一的。
·p、q 为不同节点且均存在于给定的二叉搜索树中。
● 解题思路
在二叉树的最近公共祖先中,我们递归判断左右子树中是否存在p或q
是否存在进行判断。
而基于二叉搜索树独有的性质,我们可以在遍历过程进行简化:
(1)二叉搜索树的左子树结点值均小于根结点;
(2)二叉搜索树的右子树结点值均大于根结点;
(3)二叉搜索树的左右子树均为二叉搜索树;
因此,当遍历结点的值同时大于p和q
时,最近公共祖先一定在左子树;当遍历结点的值同时小于p和q
时,最近公共祖先一定在右子树。
或许对遍历的第一个满足cur->val > p->val && cur->val < p->val
的结点是否就是最近结点产生疑问,我们不妨可以模拟证明:
如图,结点2是第一个满足cur->val > p->val && cur->val < p->val
的结点,如果继续向左遍历,在左子树会错过q,同时如果向右遍历,则在右子树上错过q,因此可以证明满足的第一个结点就是最近公共祖先。
同时处理能否将当cur == p或cur == q
的情况包含在内:
当我们遍历到结点4时,将不满足条件(root->val > p->val && root->val > q->val)和(root->val < p ->val && root->val < q->val)
,因此直接返回结点4;同时提示中:
p、q 为不同节点且均存在于给定的二叉搜索树中。
因为二叉搜索树的特点,q点一定存在于p点的子树中。
● 代码实现
方法一:递归
/**
* 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* root, TreeNode* p, TreeNode* q) {
//终止条件
if(!root) return root;
if(root->val > p->val && root->val > q->val) //左
{
TreeNode* left = lowestCommonAncestor(root->left, p, q);
if(left) return left;
}
if(root->val < p ->val && root->val < q->val) //右
{
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if(right) return right;
}
return root;
}
};
方法二:迭代
/**
* 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* root, TreeNode* p, TreeNode* q) {
TreeNode* cur = root;
while(cur)
{
if(cur->val > p->val && cur->val > q->val)
{
cur = cur->left;
}
else if(cur->val < p->val && cur->val < q->val)
{
cur = cur->right;
}
else return cur;
}
return cur;
}
};
● Leetcode701 二叉搜索树中的插入操作
题目链接:Leetcode701 二叉搜索树中的插入操作
视频讲解:代码随想录|二叉搜索树中的插入操作
题目描述:给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
示例 1:
输入:root = [4,2,7,1,3], val = 5
输出:[4,2,7,1,3,5]
解释:另一个满足题目要求可以通过的树是:
示例 2:
输入:root = [40,20,60,10,30,50,70], val = 25
输出:[40,20,60,10,30,50,70,null,null,25]
示例 3:
输入:root = [4,2,7,1,3,null,null,null,null,null,null], val = 5
输出:[4,2,7,1,3,5]
提示:
· 树中的节点数将在 [0, 104]的范围内。
· -108 <= Node.val <= 108
· 所有值 Node.val 是 独一无二 的。
· -108 <= val <= 108
· 保证 val 在原始BST中不存在。
● 解题思路
因为是对二叉搜索树进行插入元素,在遍历过程中比较结点大小和插入值大小判断向左遍历还是向右遍历,找到插入位置后构建结点完成;
需要注意的是:
当递归返回值为Treenode*
时,递归结束后需要向上一层返回,因此在构建结点之后需要将结点返回给上一层结点,至于是上一层的左孩子还有右孩子,在向下递归的过程做出了判断;
但使用迭代完成本题,则需要记录父结点的值,方便将构建的结点与父结点连接在一起。
● 代码实现
方法一:递归
/**
* 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* insertIntoBST(TreeNode* root, int val) {
TreeNode* cur = root;
if(!cur)
{
return new TreeNode(val);
}
if(cur->val > val) cur->left = insertIntoBST(cur->left ,val);
if(cur->val < val) cur->right = insertIntoBST(cur->right, val);
return root;
}
};
方法二:迭代
/**
* 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* insertIntoBST(TreeNode* root, int val) {
if(!root) return new TreeNode(val);
TreeNode* cur = root, *parent = root;
while(cur)
{
parent = cur;
if(val < cur->val)
{
cur = cur->left;
}
else
{
cur = cur->right;
}
}
TreeNode* node = new TreeNode(val);
if(parent->val > val) parent->left = node;
else parent->right = node;
return root;
}
};
● Leetcode450 删除二叉搜索树中的节点
题目链接:Leetcode450 删除二叉搜索树中的节点
视频讲解:代码随想录|删除二叉搜索树中的节点
题目描述:给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点;
如果找到了,删除它。
示例 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]。
示例 2:
输入: root = [5,3,6,2,4,null,7], key = 0
输出: [5,3,6,2,4,null,7]
解释: 二叉树不包含值为 0 的节点
示例 3:
输入: root = [], key = 0
输出: []
提示:
节点数的范围 [0, 104].
-105 <= Node.val <= 105
节点值唯一
root 是合法的二叉搜索树
-105 <= key <= 105
● 解题思路
对于二叉搜索树插入元素,因为是在叶子结点插入的,不会对二叉搜索树的结构进行改变;
但二叉搜索树删除元素,可能会改变二叉搜索树的结构,导致其不是二叉搜索树,因此在删除结点之后需要对其进行一点小小的调整。
二叉搜索树删除元素会有五种情况:
(1)根结点为空:没有可删除的元素,直接返回nullptr;
(2)删除叶子结点:因为是对叶子结点进行删除,不会改变二叉搜索树的结构,因此直接删除即可;
(3)删除结点左子树不为空,右子树为空:对于该类结点称为左单分支结点,但处理也是非常简单,因此当我们删除当前结点之后,其分支依旧能与原二叉搜索树构成二叉搜索树,不会改变结构,所以将其父结点的左孩子指向删除结点的左孩子即可;
(4)删除结点左子树为空,右子树不为空:同情况三,我们将父结点的右孩子指向删除结点的右孩子即可;
(5)删除结点左子树不为空,右子树不为空:这类结点的删除会导致二叉搜索树的结构发生改变,因此我们需要在删除结点之前做一点小处理。
我们需要先找到右子树的最左结点,因为二叉搜索树右子树的最左结点值一定是大于其左子树所有结点但在右子树是最小值,然后将左子树全部嫁接到该结点,此操作就是保证二叉搜索树在删除结点后仍然是二叉搜索树,再删除结点即可。
● 代码实现
方法一:递归
/**
* 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) return nullptr;
if(root->val == key)
{
//删除叶子结点
if(!root->left && !root->right)
{
delete root;
return nullptr;
}
//删除左单分支
else if(root->left && !root->right)
{
TreeNode* node = root->left;
delete root;
return node;
}
else if(!root->left && root->right)
{
TreeNode* node = root->right;
delete root;
return node;
}
else
{
TreeNode* cur = root->right;
while(cur->left)
{
cur = cur->left;
}
cur->left = root->left;
TreeNode* tmp = root;
root = root->right;
delete tmp;
return root;
}
}
if(root->val > key) root->left = deleteNode(root->left, key);
if(root->val < key) root->right = deleteNode(root->right, 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;
}
};