今日任务:
1)235. 二叉搜索树的最近公共祖先
2)701.二叉搜索树中的插入操作
3)450.删除二叉搜索树中的节点
235. 二叉搜索树的最近公共祖先
题目链接:235. 二叉搜索树的最近公共祖先 - 力扣(LeetCode)
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3 解释: 节点 5 和节点 1 的最近公共祖先是节点 3。示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5 解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。
文章讲解:代码随想录 (programmercarl.com)
视频讲解:二叉搜索树找祖先就有点不一样了!| 235. 二叉搜索树的最近公共祖先哔哩哔哩bilibili
思路:
这题与昨天打卡的236.二叉树的最近公共祖先这题比较像
在236题中提到
如果某节点node为p,q的最近公共祖先,那么p,q会出现以下三种情况:
1.p 和 q 在 node 的子树中,且分列 node 的异侧(即分别在左、右子树中);
2.p=node,且 q 在 node 的左或右子树中;
3.q=node,且 p 在 node 的左或右子树中;在236题中是普通二叉树,没有顺序,所以我们需要按顺序遍历节点找到p,q
在今天这题中,我们可以利用搜索二叉树的特点
- 节点的左子树只包含小于当前节点的数。
- 节点的右子树只包含大于当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
所以我们通过比较p,q,与当前节点的大小即可快速找p,q
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def lowestCommonAncestor2(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
# 如果p,q同在左侧,则继续往左子树找
if root.val > p.val and root.val > q.val:
return self.lowestCommonAncestor(root.left, p, q)
# 如果p,q同在右侧,则继续往右子树找
elif root.val < p.val and root.val < q.val:
return self.lowestCommonAncestor(root.right, p, q)
# q,p在异侧,则返回当前节点;或者root为空,返回空(返回root也就是返回空)
else:
return root
这题也可以使用迭代法,因为搜索二叉树有序,不存在回溯
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if not root:
return root
while root:
if root.val > p.val and root.val > q.val:
root = root.left
elif root.val < p.val and root.val < q.val:
root = root.right
else:
return root
# 改进
def lowestCommonAncestor2(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
while root:
if root.val > p.val and root.val > q.val:
root = root.left
elif root.val < p.val and root.val < q.val:
root = root.right
else:
break
return root
701.二叉搜索树中的插入操作
题目链接:701. 二叉搜索树中的插入操作 - 力扣(LeetCode)
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据保证,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回任意有效的结果。
文章讲解:代码随想录 (programmercarl.com)
视频讲解:原来这么简单? | LeetCode:701.二叉搜索树中的插入操作哔哩哔哩bilibili
思路:
我们不考虑题目中提示所说的改变树的结构的插入方式,插入值一定可以找到合适的叶子节点位置
实现过程:
1)只要按照二叉搜索树的规则去遍历,当遇到空节点时,也就是我们要插入的位置,我们返回插入点即可。此时还不知道是左节点还是右节点。
2)判断插入值和当前节点的大小,如果小于当前节点,则表明应该是当前节点的左孩子接受返回值
3)如果大于当前节点,则表明应该是当前节点的右孩子接受返回值
4)最后返回root即可(如果根节点本身为空,则出现我们第一步,返回插入点)
class Solution2:
def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
# 当遇到空节点时,则将插入值变为节点
if not root:
root = TreeNode(val)
return root
# 如果当前节点大于插入节点值,则将插入值赋给当前节点的左孩子
if root.val > val:
root.left = self.insertIntoBST(root.left, val)
# 如果当前节点小于插入节点值,则将插入值赋给当前节点的右孩子
if root.val < val:
root.right = self.insertIntoBST(root.right, val)
return root
也可以用迭代法,思路与之前一样,这里我是在判断过程中就赋予了值,成功插入即返回结果。
class Solution3:
def insertIntoBST(self, root, val):
if root is None: # 如果根节点为空,创建新节点作为根节点并返回
node = TreeNode(val)
return node
# 如果节点不为空,一直遍历直到找到空节点
node = root # 最后要返回根节点,为了不改变root,重新定一个变量
while node:
if node.val > val:
if node.left is None:
node.left = TreeNode(val)
break # 找到则跳出循环
else:
node = node.left
else:
if node.right is None:
node.right = TreeNode(val)
break
else:
node = node.right
return root
代码随想录提供的代码是采用两个指针记录的当前节点与父节点,当前节点为空时,则找到插入点,比较父节点与插入值大小,将父节点指向插入值即可,详细代码见代码随想录
450.删除二叉搜索树中的节点
题目链接:450. 删除二叉搜索树中的节点 - 力扣(LeetCode)
文章讲解:代码随想录 (programmercarl.com)
视频讲解:调整二叉树的结构最难!| LeetCode:450.删除二叉搜索树中的节点哔哩哔哩bilibili
思路:
这题比较有意思,不像插入节点,直接插入到叶子节点,这题删除一个节点,可能会导致树的结构变化
二叉搜索树中删除节点遇到的情况都搞清楚。
有以下五种情况:
- 找到删除的节点
- 第一种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
- 第二种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
- 第三种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
- 第四种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。
- 第五种情况:没找到删除的节点,遍历到空节点直接返回了
第四种情况比较复杂,删除节点左不空右不空,将右子树继位(也可以左子树继位)。我们需要将删除节点的左子树,给移到右子树上。根据搜索二叉树的特性,应该把左子树移到右子树左底层(只有左底层是略微大于删除节点的,左子树是略微小于删除节点,所以应该把左子树放到右子树的左底层)。然后再将上上节点指向当前节点
动画中的二叉搜索树中,删除元素7, 那么删除节点(元素7)的左孩子就是5,删除节点(元素7)的右子树的最左面节点是元素8。
将删除节点(元素7)的左孩子放到删除节点(元素7)的右子树的最左面节点(元素8)的左孩子上,就是把5为根节点的子树移到了8的左孩子的位置。
要删除的节点(元素7)的右孩子(元素9)为新的根节点。.
这样就完成删除元素7的逻辑,
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
# 如果节点为空
if not root:
return root
# 找到删除节点
if root.val == key:
# 情况一:删除节点左空右空(即跟节点),直接删除,返回空值
if root.left is None and root.right is None:
return None
# 情况二:删除节点左空右不空,将右节点返回
elif root.left is None:
return root.right
# 情况三:删除节点左不空右空,将左节点返回
elif root.right is None:
return root.left
# 情况四:删除节点左不空右不空,将右子树继位(也可以左子树继位)
# 将删除节点的左子树,给移到右子树上。根据搜索二叉树的特性,应该把左子树移到右子树左底层(只有左底层是略微大于删除节点的,左子树是略微小于删除节点,所以应该把左子树放到右子树的左底层)
else:
cur = root.right
while cur.left is not None:
cur = cur.left
cur.left = root.left
return root.right
if root.val > key:
root.left = self.deleteNode(root.left, key)
if root.val < key:
root.right = self.deleteNode(root.right, key)
return root
感想:多做多做多做!