1 今日打卡题
二叉搜索树的最近公共祖先 235. 二叉搜索树的最近公共祖先 - 力扣(LeetCode)
二叉搜索树的插入操作 701. 二叉搜索树中的插入操作 - 力扣(LeetCode)
删除二叉搜索树的节点 450. 删除二叉搜索树中的节点 - 力扣(LeetCode)
2 二叉搜索树的最近公共祖先
2.1 思路
递归终止:遇到空节点返回 null(边界处理);
向左递归:当前节点值同时大于 p/q → 公共祖先在左子树;
向右递归:当前节点值同时小于 p/q → 公共祖先在右子树;
终止递归:既不满足向左也不满足向右 → 当前节点就是 LCA(要么是 p/q 本身,要么是 p/q 分属左右子树的分叉点)。第一次出现cur在p和q之间,那么说明cur就是LCA。
2.2 实现代码
class Solution {
/**
* 核心方法:找BST中p和q的最近公共祖先
* @param root 二叉搜索树的根节点
* @param p 目标节点1
* @param q 目标节点2
* @return p和q的最近公共祖先节点
*/
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// 递归终止条件:遍历到空节点,直接返回null(BST中p/q必存在,实际不会走到此分支返回)
if(root == null) return root;
// 情况1:当前节点值同时大于p和q的值 → 公共祖先在左子树,递归遍历左子树
if(root.val > p.val && root.val > q.val) {
TreeNode left = lowestCommonAncestor(root.left, p, q);
// 左子树找到结果则直接返回(剪枝,无需继续遍历)
if(left != null) return left;
}
// 情况2:当前节点值同时小于p和q的值 → 公共祖先在右子树,递归遍历右子树
if(root.val < p.val && root.val < q.val) {
TreeNode right = lowestCommonAncestor(root.right, p, q);
// 右子树找到结果则直接返回
if(right != null) return right;
}
// 情况3:其他场景(当前节点值在p/q之间,或当前节点就是p/q)→ 此节点就是最近公共祖先
return root;
}
}
2.3 迭代法代码
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while(true) {
if(root.val > p.val && root.val > q.val) {
root = root.left;
}else if(root.val < p.val && root.val < q.val) {
root = root.right;
}else{
break;
}
}
return root;
}
}
3 二叉搜索树的插入操作
3.1 思路
核心逻辑:BST 插入的关键是利用「左小右大」特性找到唯一的空插入位置,新节点始终作为叶子节点插入,无需调整树结构;
关键注意点:
空树需单独处理,直接返回新节点;
迭代时必须用pre记录父节点,否则无法挂载新节点;
BST 约定无重复值,无需处理val == cur.val的情况(若需支持重复值,可约定挂到左 / 右子树);
3.2 迭代法实现代码
class Solution {
/**
* 向二叉搜索树(BST)中插入值为val的新节点
* 核心规则:BST左子树<根<右子树,插入后仍保持BST特性
* @param root BST根节点
* @param val 要插入的节点值
* @return 插入后的BST根节点(空树时返回新节点)
*/
public TreeNode insertIntoBST(TreeNode root, int val) {
// 1. 处理空树:直接返回新节点作为根
if (root == null) {
return new TreeNode(val);
}
// 2. 迭代找插入位置:cur遍历树,pre记录父节点
TreeNode cur = root;
TreeNode pre = null;
while (cur != null) {
pre = cur; // 记录当前节点为父节点
if (cur.val > val) {
// 目标值更小,向左子树找
cur = cur.left;
} else {
// 目标值更大,向右子树找(BST无重复值,无需处理等于情况)
cur = cur.right;
}
}
// 3. 插入新节点:根据val和父节点的大小关系挂到左/右
TreeNode newNode = new TreeNode(val);
if (val > pre.val) {
pre.right = newNode;
} else {
pre.left = newNode;
}
// 4. 返回原根节点(空树情况已提前处理)
return root;
}
}
3.3 递归法实现
分解问题:把 “插入到整棵树” 拆解为 “插入到左子树 / 右子树”,直到找到空节点(插入位置);
BST 特性:左子树值 < 根值 < 右子树值,通过值的比较确定递归方向;
递归终止条件:遇到空节点时,创建新节点并返回(这就是要插入的位置);
回溯挂载:递归返回时,把新节点(或子树)挂载到父节点的左 / 右指针上。
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) {
TreeNode node = new TreeNode(val);
return node;
}
if(root.val > val) root.left = insertIntoBST(root.left, val);
if(root.val < val) root.right = insertIntoBST(root.right, val);
return root;
}
}
4 删除二叉搜索树的节点
4.1 思路
删除节点分 3 种核心情况,代码就是按这 3 种情况处理的:
要删的节点是「叶子节点」(左右子树都为空)→ 直接删(返回 null);
要删的节点只有「左子树 / 右子树」→ 用子树直接替换它;
要删的节点「左右子树都有」→ 找右子树的 “最小节点”(右子树最左节点),把左子树挂到这个最小节点下,再用右子树替换原节点。
4.2 实现代码
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
// 递归终止条件:遍历到空节点,说明没找到要删的key,直接返回null
if (root == null) return root;
// 核心:找到要删除的节点(root.val == key)
if (root.val == key) {
// 情况1:要删的节点没有左子树 → 直接返回右子树(相当于用右子树替换当前节点)
if (root.left == null) {
return root.right;
}
// 情况2:要删的节点没有右子树 → 直接返回左子树(相当于用左子树替换当前节点)
else if (root.right == null) {
return root.left;
}
// 情况3:要删的节点左右子树都有 → 核心难点
else {
// 步骤1:找右子树的“最小节点”(右子树最左侧的节点)
TreeNode cur = root.right;
while (cur.left != null) {
cur = cur.left; // 一直往左走,直到找到右子树的最小值
}
// 步骤2:把原节点的左子树,挂到这个最小节点的左子树位置
cur.left = root.left;
// 步骤3:用原节点的右子树,替换原节点的位置(完成删除+替换)
root = root.right;
// 返回替换后的节点
return root;
}
}
// 还没找到要删的节点:按BST特性递归找
if (root.val > key) {
// 当前节点值 > key → 去左子树找,删除后更新左子树
root.left = deleteNode(root.left, key);
}
if (root.val < key) {
// 当前节点值 < key → 去右子树找,删除后更新右子树
root.right = deleteNode(root.right, key);
}
// 回溯返回:当前节点不是要删的,直接返回自身
return root;
}
}
4.3 模拟

情况 1:删除「叶子节点」(左右子树都为空)
模拟场景
删除 key=3(叶子节点,无左右子树)。


删除后:

情况 2:删除「单支节点」(只有左 / 右子树)
模拟场景
先删除 3(得到情况 1 的树),再删除 key=6



情况 3:删除「双支节点」(左右子树都有)
模拟场景
删除 key=7(此时 7 的左子树是 6,右子树是 9/8/10,左右都有):

浙公网安备 33010602011771号