目录
一、做题心得
今天是代码随想录打卡的第20天,来到了二叉树章节的part7。今天紧接上次打卡的二叉树最近公共祖先,迎来了二叉搜索树的最近公共祖先,然后就是关于二叉搜索树的两个操作:插入和删除操作。
好了,话不多说,直接看题。
二、题目与题解
题目一:235. 二叉搜索树的最近公共祖先
题目链接
235. 二叉搜索树的最近公共祖先 - 力扣(LeetCode)
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 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 为不同节点且均存在于给定的二叉搜索树中。
题解:递归
先回顾下上次打卡解决所有二叉树最近公共祖先问题都适用的做法吧。
附个题目链接:236. 二叉树的最近公共祖先 - 力扣(LeetCode)
上次已经提到,通过后序遍历可以实现自底向上的遍历,这时候判断p,q位于左还是右子树,通过不断递归,就很容易得到最后的答案。
代码如下:
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == nullptr || p->val == root->val || q->val == root->val) return root;
TreeNode* left = lowestCommonAncestor(root->left,p,q); //左
TreeNode* right = lowestCommonAncestor(root->right,p,q); //右
if (left && right) return root; //根
if (left && !right) return left;
return right;
}
};
这里的话,这道题已经明确了是二叉搜索树,那么就存在左子结点<当前节点<右子节点,根据这一特性,通过比较大小,我们就能轻松的找到p,q究竟是位于左子树还是右子树,然后同样根据递归得到结果既可。
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == nullptr || p->val == root->val || q->val == root->val) return root; //根节点为空或者p或q为根节点时
if (p->val < root->val && q->val < root->val) return lowestCommonAncestor(root->left,p,q); //p,q都在左子树
if (p->val > root->val && q->val > root->val) return lowestCommonAncestor(root->right,p,q); //p,q都在右子树
return root; //p,q一个在左子树,一个在右子树
}
};
题目二: 701.二叉搜索树中的插入操作
题目链接
701. 二叉搜索树中的插入操作 - 力扣(LeetCode)
给定二叉搜索树(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中不存在。
题解1:递归
这个题的递归其实很好理解,因为题目已经明确说是对二叉搜索树进行插入操作。然后我们可以根据二叉搜索树的性质,通过将目标值与当前节点的值进行比较,比当前节点值小就插入左子树中,比当前节点值大就插入右子树中,通过递归操作在最后(当前节点即将为空时)将目标值的新节点插入进去。
代码如下:
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (root == nullptr) return new TreeNode(val); //递归终止条件:找到遍历的节点为空的时候,就是要插入节点的位置了,并把插入的节点返回。
if (val < root->val) root->left = insertIntoBST(root->left, val); //左:val插入进左子树,左子树发生改变
if (val > root->val) root->right = insertIntoBST(root->right, val); //右:val插入进右子树,右子树发生改变
return root;
}
};
题解2:迭代
插入节点
迭代的话其实也简单,但是要注意一个问题,这里先看看我的错误代码:
像我这样做的话,一方面是麻烦了,我一开始想的既然在最后插入节点,就一层一层遍历就好了,然后就直接套层序遍历模板,但的确多此一举;另一当面, 还做错了,大家仔细看下就知道,我这样做,只满足了插入的节点比相邻层的根节点(即上一层根节点)更大或更小,并不一定满足比不是相邻层的较远的根节点更大或者更小。总之,就是写了一坨。
这里看看官方的代码:
其实思路上很简单,我们先通过比较val与root->val的大小,再确定此时节点是叶子节点就可以直接插入节点了,如果不是叶子节点的话,也只需要在上边大小比较的前提上将当前节点依次往下一层遍历即可。
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (root == nullptr) return new TreeNode(val);
TreeNode* node = root; //node记录当前节点
while (node != nullptr) {
if (val < node->val) {
if (node->left == nullptr) {
node->left = new TreeNode(val); //插入值为val的节点
return root; //插入节点完之后直接返回既可
}
else node = node->left; //不为空就继续遍历
}
else {
if (node->right == nullptr) {
node->right = new TreeNode(val);
return root;
}
else node = node->right;
}
}
return root;
}
};
题目三:450.删除二叉搜索树中的节点
题目链接
450. 删除二叉搜索树中的节点 - 力扣(LeetCode)
给定一个二叉搜索树的根节点 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
进阶: 要求算法时间复杂度为 O(h),h 为树的高度。
题解:递归
删除节点
相对于插入节点,二叉搜索树节点的删除就要复杂的多。但本质就是理解怎么删,删完后二叉树怎么合在一起,而且要满足删除节点并合并后还是满足为二叉搜索树。
这里我们分为几种情况:
1.当前(根)节点为空:没有可删除的节点,直接返回根节点(空节点)
2.当前节点的值等于val(即要删除当前节点):
(1)左右子树为空:删除当前节点后为空
(2)左子树或者右子树为空:删除当前节点后,返回不为空的子树既可
(3)左右子树都不为空(建议画图理解):先用cur指针找到并记录当前节点右子树的最小值对应节点(同时也是最左边的节点),然后将右子树中的最小节点即cur对应的节点(右子树最左节点)的左子树(原本为空)连接到当前节点的左子树上 ,再使当前节点的右子节点作为新的根节点,即可。
3.val小于或者大于当前节点值(递归操作):
(1)val小于当前节点值:说明要删除的值在左子树里
(2)val大于当前节点值:说明要删除的值在右子树里
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if (!root) return root; //当前节点为空
if (root->val == key) { //当前节点的值等于key时
if (!root->left && !root->right) return nullptr; //左右子树都为空,删除当前节点后则空
else if (root->left && !root->right) return root->left; //左子树不空,右子树空,删除当前节点后只剩左子树
else if (!root->left && root->right) return root->right;
else { //左右子树都不为空
TreeNode* cur = root->right;
while (cur->left) { //利用二叉搜索树性质:新建节点cur用来寻找右子树中的最小节点(即右子树的最左节点)
cur = cur->left;
}
cur->left = root->left; //将右子树中的最小节点(右子树最左节点)的左子树连接到当前节点的左子树上
root = root->right; //连接后,新的根节点就变成了root->right
return root;
}
}
//递归(分别对左右子树)
if(key < root->val) root->left = deleteNode(root->left, key); //key值对应的节点在左子树时
if(key > root->val) root->right = deleteNode(root->right, key); //key值对应的节点在右子树时
return root;
}
};
三、小结
今天的打卡到此也就结束了,二叉树的学习也进入了一个新的阶段,至少已经能够自己慢慢想出各种递归的应用了。最后,我是算法小白,但也希望终有所获。