第二十二天| 第六章 二叉树part08 235. 二叉搜索树的最近公共祖先 701.二叉搜索树中的插入操作 450.删除二叉搜索树中的节点
一、235. 二叉搜索树的最近公共祖先
-
题目链接:https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/
-
题目介绍:
-
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
-
-
思路:
- 二叉搜索树的性质决定了,左子树上的值都小于根节点,右子树上的值都大于根节点,所以我们要利用这个特性
- 如果p.val和q.val都小于root.val,那么最近公共祖先一定在左子树上
- 如果p.val和q.val都大于root.val,那么最近公共祖先一定在右子树上
- 如果root.val在p.val和q.val或者q.val和p.val之间,那么找到的第一个符合此条件的根节点就是最近公共祖先
- 二叉搜索树的性质决定了,左子树上的值都小于根节点,右子树上的值都大于根节点,所以我们要利用这个特性
-
代码:
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) return null;
if (root.val > p.val && root.val > q.val) {
TreeNode left = lowestCommonAncestor(root.left, p, q);
if (left != null) return left;
}
if (root.val < p.val && root.val < q.val) {
TreeNode right = lowestCommonAncestor(root.right, p, q);
if (right != null) return right;
}
return root;
}
}
二、701.二叉搜索树中的插入操作
-
题目链接:https://leetcode.cn/problems/insert-into-a-binary-search-tree/
-
题目介绍:
-
给定二叉搜索树(BST)的根节点
root
和要插入树中的值value
,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
-
-
思路:
- 新的节点一定可以在二叉搜索树的叶子节点创建
- **因此如果遇到null,就创建一个新的节点。**然后不断地把当前的root返回个给上一层
- 具体在左子树还是右子树,就需要判断给定的val和root.val的大小
-
代码:
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) {
TreeNode node = new TreeNode(val);
return node;
}
if (val < root.val) {
root.left = insertIntoBST(root.left, val);
}
if (val > root.val) {
root.right = insertIntoBST(root.right, val);
}
return root;
}
}
三、450.删除二叉搜索树中的节点
-
题目链接:https://leetcode.cn/problems/delete-node-in-a-bst/description/
-
题目描述:
-
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
- 首先找到需要删除的节点;
- 如果找到了,删除它。
-
-
思路:
- (1)递归函数的参数:LeetCode已给出
- (2)递归的终止条件:本题中我们不需要遍历整个二叉树,只要找到要删除的节点,执行XXXX,即可终止
- 这里包括两大类,五种情况
- 两大类分别是没找到和找到了
- 五种情况分别是:没找到、找到了且左右皆空、找到了左空右不空、找到了左不空右空、找到了左不空右不空。
- 其中,最后一种的逻辑最难处理需要分为三步(在代码中有详细的注释)
-
代码:
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
// (1) 递归结束的条件: 执行删除节点的逻辑
// 第一类第一种情况:没有找到key值对应的节点
if (root == null) return null;
// 第二类:找到了
if (key == root.val) {
// 第二种情况:找到了,并且为叶子节点 (左右皆空)
if (root.left == null && root.right == null) {
// 这里返回一个null,是因为后续单层逻辑处理的时候会让上一层的root的左或者右接住
return null;
}
// 第三种情况:找到了,并且左节点不为空,右节点为空
else if (root.left != null && root.right == null) {
// 跳过当前节点,给该节点的上一层直接返回其左节点
return root.left;
}
// 第四种情况:找到了,并且左节点为空,右节点不为空
else if (root.left == null && root.right != null) {
// 和第三种情况的逻辑一致
return root.right;
}
// 第五种情况:找到了,并且左右都不空
// 具体操作为:
// 5.1 找到当前节点右子树的左下角的节点
// 5.2 将左子树接在该节点的left
// 5.3 删除该节点 (此时删除的逻辑类似于左空,右不空)
else {
TreeNode cur = root.right;
while (cur.left != null) {
cur = cur.left;
}
cur.left = root.left;
return root.right;
}
}
// (2) 单层递归的逻辑
if (key < root.val) {
root.left = deleteNode(root.left, key);
}
if (key > root.val) {
root.right = deleteNode(root.right, key);
}
// 最后处理完返回整棵树的根节点
return root;
}
}
总结:二叉搜索树的插入和删除操作中都有一个共同点就是,找到在树中找到目标值之后,需要返回一个TreeNode,这个TreeNode需要给上一层的root接住,即处理单层逻辑的时候,需要用上一层的root.left或者root.right接住下一层的返回值TreeNode。
(接住这个词是二叉搜索树增加和删除节点的核心)