返回值是非常有效的传递信息的方式!尤其在二叉树的修改中,返回值可以帮助以重构的方式完成修改,而无需保持双指针进行修改(树的结构决定了双指针还需要父节点的方向,太繁琐)。
题目链接 | 解题思路
和 236 的思路是一致的,但是 BST 的特殊性质能够提供更快的剪枝:
在 BST 中,自上往下第一次遇到 min(p, q) < node.val < max(p, q)
的时候,就能确定该节点是 p 和 q 的最近公共祖先。否则,根据 BST 的有序性只需要遍历当前节点的一个子树即可。
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if root == None:
return None
if root == p or root == q:
return root
min_val, max_val = min(p.val, q.val), max(p.val, q.val)
if min_val > root.val:
return self.lowestCommonAncestor(root.right, p, q)
if max_val < root.val:
return self.lowestCommonAncestor(root.left, p, q)
return root
题目链接 | 解题思路
class Solution:
def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
if root == None:
return TreeNode(val=val)
if root.val > val:
if root.left == None:
root.left = TreeNode(val=val)
else:
self.insertIntoBST(root.left, val)
if root.val < val:
if root.right == None:
root.right = TreeNode(val=val)
else:
self.insertIntoBST(root.right, val)
return root
简化版本如下,函数的返回值体现了对新节点的赋值和对父节点的更新。单层递归的逻辑是标准的“遍历一条边”,更新当前节点的左右子树能够“接住”递归函数的返回值,来完成节点的添加。
class Solution:
def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
if root == None:
return TreeNode(val=val)
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 deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
if root == None:
return None
if root.val == key:
# case 1: leaf node to delete
if root.left == None and root.right == None:
return None # ignore this node when reconstruct the tree
# case 2: left is None, right is not None
elif root.left == None and root.right != None:
return root.right
# case 3: left is not None, right is None
elif root.left != None and root.right == None:
return root.left
# case 4: a common node with both children
else:
curr_node = root.right
while (curr_node.left != None):
curr_node = curr_node.left
curr_node.left = root.left
return root.right
# now all the termination cases are done, recursion if no termination
if root.val > key:
root.left = self.deleteNode(root.left, key)
if root.val < key:
root.right = self.deleteNode(root.right, key)
return root
如果是在普通二叉树中进行节点删除,可以通过交换值的方式:
这里的主函数中记录了待删除节点的父节点,以此来进行节点的删除,可能更容易想到,但实现起来实在是麻烦。
class Solution:
def deleteOneNode(self, target: Optional[TreeNode]) -> Optional[TreeNode]:
# case 1: meet the None
if target == None:
return None
# case 2: right is None, directly use left subtree to replace
if target.right == None:
return target.left
# case 3: right is not None, find the smallest node in right subtree
curr = target.right
while (curr.left != None):
curr = curr.left
curr.left = target.left
return target.right
def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
if root == None:
return None
pre, curr = None, root # pre records the parent of curr
while (curr != None):
if curr.val == key:
break
pre = curr
if curr.val > key:
curr = curr.left
else:
curr = curr.right
# curr = root, the whole tree has only the root, or root is the node to delete
if pre == None:
return self.deleteOneNode(curr)
# need to know which way (left or right) is the curr relative to pre
# case: curr = pre.left
if pre.left != None and pre.left.val == key:
pre.left = self.deleteOneNode(curr)
# case: curr = pre.right
if pre.right != None and pre.right.val == key:
pre.right = self.deleteOneNode(curr)
return root