思路:
本题是二叉搜索树,二叉搜索树是有序的,那得好好利用一下这个特点。
因为是有序树,所以 如果 中间节点是 q 和 p 的公共祖先,那么 中节点的数组 一定是在 [p, q]区间的。即 中节点 > p && 中节点 < q 或者 中节点 > q && 中节点 < p。
那么只要从上到下去遍历,遇到 cur节点是数值在[p, q]区间中则一定可以说明该节点cur就是p 和 q的公共祖先。
而递归遍历顺序,本题就不涉及到 前中后序了(这里没有中节点的处理逻辑,遍历顺序无所谓了)。
对于二叉搜索树的最近祖先问题,其实要比普通二叉树公共祖先问题 (opens new window)简单的多。不用使用回溯,二叉搜索树自带方向性,可以方便的从上向下查找目标区间,遇到目标区间内的节点,直接返回。
代码:
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
return self.traversal(root, p, q)
def traversal(self, cur, p, q):
# 如果当前节点为空,说明路径上不存在p和q,返回None
if cur is None:
return None
# 如果当前节点的值大于p和q的值,说明p和q都在当前节点的左子树中
if cur.val > p.val and cur.val > q.val:
left = self.traversal(cur.left, p, q) # 递归地在左子树中查找最低公共祖先
if left is not None: # 如果在左子树中找到了最低公共祖先,返回它
return left
# 如果当前节点的值小于p和q的值,说明p和q都在当前节点的右子树中
if cur.val < p.val and cur.val < q.val:
right = self.traversal(cur.right, p, q) # 递归地在右子树中查找最低公共祖先
if right is not None: # 如果在右子树中找到了最低公共祖先,返回它
return right
# 如果以上两种情况都不满足,说明当前节点就是p和q的最低公共祖先
return cur
时间复杂度:O(n),其中 n为二叉树的节点个数。
空间复杂度:O(n),最坏情况下,二叉树是一条链,因此递归需要 O(n) 的栈空间。
思路:
只要按照二叉搜索树的规则去遍历,遇到空节点就插入节点就可以了。
例如插入元素10 ,需要找到末尾节点插入便可,一样的道理来插入元素15,插入元素0,插入元素6,需要调整二叉树的结构么? 并不需要
代码:
class Solution:
def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
# 如果当前树为空(即根节点为None),则创建一个新的节点并返回它
if root is None:
node = TreeNode(val)
return node
# 如果当前根节点的值大于要插入的值val,则val应该被插入到左子树中。因此,递归调用insertIntoBST方法,将val插入到左子树,并更新当前根节点的左子节点。
if root.val > val:
root.left = self.insertIntoBST(root.left, val)
# 如果当前根节点的值小于要插入的值val,则val应该被插入到右子树中。因此,递归调用insertIntoBST方法,将val插入到右子树,并更新当前根节点的右子节点
if root.val < val:
root.right = self.insertIntoBST(root.right, val)
# 返回更新后的根节点
return root
时间复杂度:O(n)
空间复杂度:O(n)
思路:
搜索树的节点删除要比节点增加复杂的多,有很多情况需要考虑,做好心理准备。因为二叉搜索树添加节点只需要在叶子上添加就可以的,不涉及到结构的调整,而删除节点操作涉及到结构的调整。
这里就把二叉搜索树中删除节点遇到的情况都搞清楚。
有以下五种情况:
- 第一种情况:没找到删除的节点,遍历到空节点直接返回了
- 找到删除的节点
- 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
- 第三种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
- 第四种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
- 第五种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。
这里最关键的逻辑就是第五种情况(删除一个左右孩子都不为空的节点),这种情况一定要想清楚。
代码:
class Solution:
def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
# 如果当前节点为None(即树为空),则直接返回None
if root is None:
return None
# 如果当前节点的值等于要删除的键
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
# 把要删除的节点(root)左子树放在cur的左孩子的位置
cur.left = root.left
# 返回旧root的右孩子作为新root
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
时间复杂度:O(n)
空间复杂度:O(n)