跟着卡哥学算法Day 20:二叉树常见题目7
235. 二叉搜索树的最近公共祖先 🌟🌟
力扣链接 🌟🌟
题目描述
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 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 为不同节点且均存在于给定的二叉搜索树中。
// 输出 树根节点// 6// / \// 2 8// / \ / \// 0 4 7 9// / \// 3 5解题思路
二叉搜索树
-
p 和 q 值都小于当前节点值时,p 和 q 的最近公共祖先就在左子树
-
p 和 q 值都大于当前节点值时,p 和 q 的最近公共祖先就在右子树
-
否则,当前节点就是 p 和 q 的最近公共祖先(此时 p 和 q 分布在当前节点的两侧,或其中一个等于当前节点)
如上述二叉搜索树:如果 2 处于[p, q]之间时,此时 p 就在节点 2 的左子树,q 在节点 2 的右子树,那么 2 就一定是 p 和 q 的最近公共祖先。
递归三部曲:
-
明确递归函数的参数和返回值
- 参数 1:当前节点
- 参数 2:p
- 参数 3
- 返回值:最近公共节点
-
明确终止条件
- 当节点为 null 返回 null
-
确定单层递归逻辑
-
p 和 q 值都大于 当前节点值时,继续遍历左子树
-
p 和 q 值都小于 当前节点值时,继续遍历右子树
-
否则,返回当前节点
if (cur.val > p.val && cur.val > q.val)if (cur.val < p.val && cur.val < q.val)// 遍历左子树// 遍历右子树return cur
-
function lowestCommonAncestor(root, p, q) { if (!root) return null
if (root.val > p.val && root.val > q.val) { return lowestCommonAncestor(root.left, p, q) } else if (root.val < p.val && root.val < q.val) { return lowestCommonAncestor(root.right, p, q) } else { return root }}// 迭代法var lowestCommonAncestor = function (root, p, q) { if (!root) return null while (root) { if (root.val < p.val && root.val < q.val) { return lowestCommonAncestor(root.right, p, q) } if (root.val > p.val && root.val > q.val) { return lowestCommonAncestor(root.left, p, q) } return root }}701.二叉搜索树中的插入操作 🌟🌟
力扣链接 🌟🌟
题目描述
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据保证,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回任意有效的结果。
示例:
// 给定二叉搜索树:// 4// / \// 2 7// / \// 1 3// 和 插入的值:5
// 返回二叉树// 4// / \// 2 7// /\ /// 1 3 5// 或者这个树也是有效的// 5// / \// 2 7// / \// 1 3// \// 4提示:
- 给定的树上的节点数介于 0 和 10^4 之间
- 每个节点都有一个唯一整数值,取值范围从 0 到 10^8
- -10^8 <= val <= 10^8
- 新值和原始二叉搜索树中的任意节点值都不同
#
解题思路
递归
递归三部曲:
-
确定递归函数的参数和返回值
- 参数 1:当前节点
- 参数 2:要插入的值
- 返回值:root
-
确定终止条件
如果当前节点为 null,则根据值创建新节点
if (!root1) return new TreeNode(val) -
确定单层递归逻辑
- 当前节点值大于 val,继续遍历左子树
- 当前节点值小于 val,继续遍历右子树
- 否则,返回返回当前节点
function insertIntoBST(root, val) { const setInOrder = (root, val) => { if (!root) return new TreeNode(val)
if (root.val > val) root.left = setInOrder(root.left, val) if (root.val < val) root.right = setInOrder(root.right, val)
return root }
return setInOrder(root, val)}迭代
步骤:
- 记录当前节点 root 以及父节点 parent(初始化时定义)
- 循环遍历当前节点,比较当前节点值和插入值
- 如果当前节点不为空,更新当前节点为左或右子节点,同时更新父节点
- 如果当前节点为空,根据父节点的值和插入值的大小,决定插入到左还是右
- 返回 root
function insertIntoBST(root, val) { if (!root) { return new TreeNode(val) } else { let parent = null let cur = root while (cur) { parent = cur if (cur.val > val) { cur = cur.left } else { cur = cur.right } } let node = new TreeNode(val) if (parent.val > val) { parent.left = node } else { parent.right = node } } return root}450.删除二叉搜索树中的节点 🌟🌟
力扣链接 🌟🌟
题目描述
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点; 如果找到了,删除它。 说明: 要求算法时间复杂度为 ,h 为树的高度。
示例:

解题思路
删除节点情况:
- 没找到删除节点,则返回 null
- 删除节点是叶子节点,直接删除,不影响其他节点
- 删除节点只有一个子节点,删除节点,并用子节点替换删除节点
- 删除节点有两个子节点,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上(右子树中最小值),返回删除节点右孩子为新的根节点
递归
递归三部曲:
-
确定递归函数的参数和返回值
- 参数 1:根节点
- 参数 2:要删除的值
- 返回值:搜索的值所在的节点
-
确定终止条件
如果 root 为 null 则说明没找到删除的节点,返回 root
if (!root) return root -
确定单层递归逻辑
- 要删除的值大于 root.val,则继续递归右子树
- 要删除的值小于 root.val,则继续递归左子树
- 要删除的值就是当前节点时:
- 节点是叶子结点
- 节点只有一个子节点,则返回
- 左右节点都存在,将将当前节点的左子树头节点指向右子树最左边节点,并将右子树指向删除节点的父节点
function deleteNode(root, key) { if (!root) return root if (root.val > key) { root.left = deleteNode(root.left, key) } else if (root.val < key) { root.right = deleteNode(root.right, key) } else { if (!root.left && !root.right) return null if (!root.left && root.right) return root.right if (!root.right && root.left) return root.left
const rightNode = root.right const minNode = getMinNode(root.right) root.val = minNode.val root.right = deleteNode(rightNode, minNode.val) } return root}function getMinNode(node) { while (node.left) node = node.left; return node;}迭代
function deleteNodeIterative(root, key) { let parent = null; let current = root;
// 查找目标节点 while (current && current.val !== key) { parent = current; current = key < current.val ? current.left : current.right; }
if (!current) return root; // 未找到
// Case 1: 无子节点 if (!current.left && !current.right) { if (!parent) return null; // 删除根节点 parent[current === parent.left ? 'left' : 'right'] = null; return root; }
// Case 2: 单个子节点 if (!current.left || !current.right) { const child = current.left || current.right; if (!parent) return child; // 删除根节点 parent[current === parent.left ? 'left' : 'right'] = child; return root; }
// Case 3: 两个子节点 const successorParent = current; let successor = current.right; while (successor.left) { successorParent = successor; successor = successor.left; }
current.val = successor.val; if (successorParent === current) { successorParent.right = successor.right; } else { successorParent.left = successor.right; }
return root;}