提示:努力生活,开心、快乐的一天
文章目录
235. 二叉搜索树的最近公共祖先
💡解题思路
- 二叉搜索树是有序的,所有 如果 中间节点是 q 和 p 的公共祖先,那么 中节点的数组 一定是在 [p, q]区间的。即 中节点 > p && 中节点 < q 或者 中节点 > q && 中节点 < p
- 只要从上到下去遍历,遇到 cur节点是数值在[p, q]区间中则一定可以说明该节点cur就是q 和 p的公共祖先
- 递归三部曲:
- 确定递归函数返回值以及参数:参数就是当前节点,以及两个结点 p、q;返回值是要返回最近公共祖先
- 确定终止条件:遇到空返回就可以了;其实都不需要这个终止条件,因为题目中说了p、q 为不同节点且均存在于给定的二叉搜索树中。也就是说一定会找到公共祖先的,所以并不存在遇到空的情况
- 确定单层递归的逻辑:在遍历二叉搜索树的时候就是寻找区间[p->val, q->val](注意这里是左闭右闭)。1、如果 cur->val 大于 p->val,同时 cur->val 大于q->val,那么就应该向左遍历(说明目标区间在左子树上)2、如果 cur->val 小于 p->val,同时 cur->val 小于 q->val,那么就应该向右遍历(目标区间在右子树)3、cur节点在区间(p->val <= cur->val && cur->val <= q->val)或者 (q->val <= cur->val && cur->val <= p->val)中,那么cur就是最近公共祖先了,直接返回cur。
🤔遇到的问题
- 在调用递归函数的地方,把递归函数的返回值left/right,直接return,没有直接返回
💻代码实现
递归法
var lowestCommonAncestor = function (root, p, q) {
// 使用递归的方法
// 1. 使用给定的递归函数lowestCommonAncestor
// 2. 确定递归终止条件
if (root === null) return root
// 向左子树查询
if (root.val > p.val && root.val > q.val) {
return lowestCommonAncestor(root.left,p,q)
}
// 向右子树查询
if (root.val < p.val && root.val < q.val) {
return lowestCommonAncestor(root.right,p,q)
}
//(p->val <= cur->val && cur->val <= q->val)
//(q -> val <= cur -> val && cur -> val <= p -> val)
return root
};
迭代法
var lowestCommonAncestor = function(root, p, q) {
while (root) {
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 {
return root
}
}
return null
};
🎯题目总结
二叉搜索树的最近祖先问题,其实要比普通二叉树公共祖先问题简单的多,不用使用回溯,二叉搜索树自带方向性,可以方便的从上向下查找目标区间,遇到目标区间内的节点,直接返回。
701. 二叉搜索树中的插入操作
💡解题思路
- 简化题目,可以不考虑题目中提示所说的改变树的结构的插入方式,只要遍历二叉搜索树,找到空节点 插入元素就可以了
- 递归法:三部曲
- 确定递归函数参数以及返回值:参数就是根节点指针,以及要插入元素,返回值是插入的节点,利用返回值完成新加入的节点与其父节点的赋值操作
- 确定终止条件:终止条件就是找到遍历的节点为null的时候,就是要插入节点的位置了,并把插入的节点返回
- 确定单层递归的逻辑:搜索树是有方向了,可以根据插入元素的数值,决定递归方向,当node.val > val,遍历左子树;当node.val<val,遍历右子树。通过递归函数返回值完成了新加入节点的父子关系赋值操作了,下一层将加入节点返回,本层用root->left或者root->right将其接住
- 迭代法:空树的判断;需要定义一个节点pre,记录上一个节点的值,遍历节点,不需要完全遍历,根据当前节点值与输入的节点值比较,如果cur.val > val,则cur = cur.left,否则cur = cur.right。遍历结束后,pre记录了插入节点后的父节点,然后根据pre.val与val的大小片段,确定插入到pre的左边还是右边
🤔遇到的问题
- 递归时终止条件,返回新插入的节点
- 迭代法的逻辑,捋了2遍
💻代码实现
递归法
var insertIntoBST = function(root, val) {
const setInOrder = (node, val) => {
//找到遍历的节点为null的时候,
//就是要插入节点的位置了,并把插入的节点返回
if (node === null) {
let newNode = new TreeNode(val)
return newNode
}
//递归函数返回值完成了新加入节点的父子关系赋值操作了
if (node.val > val) node.left = setInOrder(node.left, val)
else if (node.val < val) node.right = setInOrder(node.right, val)
return node
}
return setInOrder(root,val)
};
迭代法
var insertIntoBST = function (root, val) {
//空树,直接插入该节点
if (root === null) {
root = new TreeNode(val)
} else {
let cur = root
//需要记录上一个节点,否则无法赋值新节点
let pre = root
while (cur) {
//遍历树,找到插入节点的父节点
pre = cur
if (cur.val > val) {
cur = cur.left
}else {
cur = cur.right
}
}
let node = new TreeNode(val)
//根据找到的pre的值与val比较,确定最终插入的位置,是左节点还是右节点
if (pre.val > val) {
pre.left = node
} else {
pre.right = node
}
}
return root
};
🎯题目总结
递归中,重点是如何通过递归函数的返回值完成新加入节点和其父节点的赋值操作,并强调了搜索树的有序性。
迭代的方法就需要记录当前遍历节点的父节点了
450. 删除二叉搜索树中的节点
💡解题思路
- 递归三部曲:
- 确定递归函数参数以及返回值:参数是root及 key;返回值为补位的节点
- 确定终止条件:遇到空返回,其实这也说明没找到删除的节点,遍历到空节点直接返回了
- 确定单层递归的逻辑:多种情况如下
1、没找到删除的节点,遍历到空节点直接返回了
2、当前节点>key,去左子树寻找key,递归
3、当前节点<key,去右子树寻找key,递归
4、当前节点=key,确定该节点的位置并进行删除
4.1、左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
4.2、其左孩子为空,右孩子不为空,删除节点,右孩子补位 ,返回右孩子为根节点
4.3、其右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
4.4、左右孩子节点都不为空,则将删除节点的左子树放到删除节点的右子树的最左面节点的左孩子的位置,并返回删除节点右孩子为新的根节点
🤔遇到的问题
- 左右孩子节点都不为空时 的判断出错啦
💻代码实现
递归法
var deleteNode = function(root, key) {
//第一种情况:没找到删除的节点,遍历到空节点直接返回了
if(root===null) return root
//当前节点>key,去左子树寻找key
if(root.val>key){
root.left = deleteNode(root.left,key)
}else if(root.val<key){
//当前节点<key,去右子树寻找key
root.right = deleteNode(root.right,key)
}else{
//找到与key相同的节点,确定该节点的位置并进行删除
//第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
if(root.left===null&&root.right===null) return null
// 第三种情况:其左孩子为空,右孩子不为空,删除节点,右孩子补位 ,返回右孩子为根节点
else if(root.left ===null&&root.right!=null){
return root.right
}
// 第四种情况:其右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
else if(root.left !=null&&root.right===null){
return root.left
}
// 第五种情况:左右孩子节点都不为空,则将删除节点的左子树放到删除节点的右子树的最左面节点的左孩子的位置
// 并返回删除节点右孩子为新的根节点。
else{
// 找右子树最左面的节点
let cur = root.right
while(cur.left){
cur = cur.left
}
// 把要删除的节点(root)左子树放在cur的左孩子的位置
cur.left = root.left
// 返回旧root的右孩子作为新root
root = root.right
return root
}
}
return root
};
🎯题目总结
删除一个左右孩子都不为空的节点时,需要找到右子树最左面的节点,把要删除的节点(root)左子树放在cur的左孩子的位置,最后返回旧root的右孩子作为新root
🎈今日心得
删除二叉搜索树中的节点,确实难