代码随想录 - Day29 - 最近公共祖先,插入操作,删除操作
236. 二叉树的最近公共祖先
自底向上查找:后序遍历(左右中)+回溯
两种情况:
- p和q分别为某个节点的左右节点,那么这个节点就是它们的最近公共祖先
- p(q)为q§的子孙节点,那么作为祖先节点的p(q)就是它们的最近公共祖先
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if root == q or root == p or not root:
return root
# 回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if left and right: # 若p和q分别为左右节点,返回它们的根节点就可以了
return root
if not left and right: # 只有右节点等于p或q
return right
elif left and not right: # 只有左节点等于p或q
return left
else: # 左右节点都不是p或q
return None
235. 二叉搜索树的最近公共祖先
- 把它当成普通二叉树,找二叉树的最近公共祖先。代码和上一题一样即可,本题就不再给出了。
- 按照二叉搜索树的特点来找,右节点全部大于根节点,左节点全部小于根节点,那么从上往下搜索时:
- 如果pq均小于当前节点,那么继续遍历左子树,如果pq均大于当前节点,那么继续遍历右子树。
- 如果
min(p.val, q.val) <= 当前节点 <= max(p.val, q.val)
,那么该节点为二叉搜索树的最近公共祖先
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if not root:
return root
if root.val > max(p.val, q.val):
return self.lowestCommonAncestor(root.left, p, q)
if root.val < min(p.val, q.val):
return self.lowestCommonAncestor(root.right, p, q)
if min(p.val, q.val) <= root.val <= max(p.val, q.val):
return root
注意:如果前面写了if root == q or root == p or not root: return root
,那么后面就只需要写<
;如果前面写if not root: return root
后面才要写<=
,要注意对应。
# 精简一下,可以写成这样:
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if root.val > max(p.val, q.val):
return self.lowestCommonAncestor(root.left, p, q)
elif root.val < min(p.val, q.val):
return self.lowestCommonAncestor(root.right, p, q)
else:
return root
# 或者这样:
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if root.val > p.val and root.val > q.val:
return self.lowestCommonAncestor(root.left, p, q)
elif root.val < p.val and root.val < q.val:
return self.lowestCommonAncestor(root.right, p, q)
else:
return root
701. 二叉搜索树中的插入操作
往简单的方向想。
其实不管插入拿个数值,都可以不改变树的结构,直接插入到空节点处。(不理解的话可以自己画图试一试)
所以写代码的时候就可以利用二叉搜索树的特点,根据其特点找到合适的空节点处进行插入操作。
class Solution:
def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
if not root:
node = TreeNode(val)
return node
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 Solution:
def __init__(self):
self.parent = None
def traversal(self, cur, val):
if cur is None: # parent左孩子或者右孩子指向新插入的节点
node = TreeNode(val)
if val > self.parent.val:
self.parent.right = node
else:
self.parent.left = node
return
self.parent = cur
if cur.val > val:
self.traversal(cur.left, val)
if cur.val < val:
self.traversal(cur.right, val)
def insertIntoBST(self, root, val):
self.parent = TreeNode(0)
if root is None:
return TreeNode(val)
self.traversal(root, val)
return root
迭代法:需要记录一下当前遍历的节点的父节点,这样才能做插入节点的操作
class Solution:
def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
if not root: # 如果根节点为空,创建新节点作为根节点并返回
node = TreeNode(val)
return node
cur = root
parent = root # 记录上一个节点,用于连接新节点
while cur:
parent = cur
if cur.val > val:
cur = cur.left
else:
cur = cur.right
node = TreeNode(val)
if val < parent.val:
parent.left = node # 将新节点连接到父节点的左子树
else:
parent.right = node # 将新节点连接到父节点的右子树
return root
450. 删除二叉搜索树中的节点
删除节点的五种情况:
- 没找到删除的节点:
- 遍历到空节点直接返回
- 找到删除的节点:
- 左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
- 删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
- 删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
- 左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。
# 返回要删除节点的下一个节点就算删除了
class Solution:
def deleteNode(self, root, key):
# 第一种情况:没找到删除的节点,遍历到空节点直接返回
if root is None:
return root
if root.val == key:
# 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
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 # 把要删除的节点(root)左子树放在cur的左孩子的位置
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
普通二叉树的删除方式:遍历整棵树,用交换值的操作来删除目标节点。
代码中目标节点(要删除的节点)被操作了两次:
- 第一次是和目标节点的右子树最左面节点交换。
- 第二次直接被NULL覆盖了。
不好想,实操性不强
class Solution:
def deleteNode(self, root, key):
if root is None: # 如果根节点为空,直接返回
return root
if root.val == key: # 找到要删除的节点
if root.right is None: # 如果右子树为空,直接返回左子树作为新的根节点
return root.left
cur = root.right
while cur.left: # 找到右子树中的最左节点
cur = cur.left
root.val, cur.val = cur.val, root.val # 将要删除的节点值与最左节点值交换
root.left = self.deleteNode(root.left, key) # 在左子树中递归删除目标节点
root.right = self.deleteNode(root.right, key) # 在右子树中递归删除目标节点
return root
迭代法:
class Solution:
def deleteOneNode(self, target: TreeNode) -> TreeNode:
"""
将目标节点(删除节点)的左子树放到目标节点的右子树的最左面节点的左孩子位置上
并返回目标节点右孩子为新的根节点
是动画里模拟的过程
"""
if target is None:
return target
if target.right is None:
return target.left
cur = target.right
while cur.left:
cur = cur.left
cur.left = target.left
return target.right
def deleteNode(self, root: TreeNode, key: int) -> TreeNode:
if root is None:
return root
cur = root
pre = None # 记录cur的父节点,用来删除cur
while cur:
if cur.val == key:
break
pre = cur
if cur.val > key:
cur = cur.left
else:
cur = cur.right
if pre is None: # 如果搜索树只有头结点
return self.deleteOneNode(cur)
# pre 要知道是删左孩子还是右孩子
if pre.left and pre.left.val == key:
pre.left = self.deleteOneNode(cur)
if pre.right and pre.right.val == key:
pre.right = self.deleteOneNode(cur)
return root
这道题不是很好理解,后续应该多看看多理解理解,掌握第一种递归方法即可。