目录
235. 二叉搜索树的最近公共祖先
701.二叉搜索树中的插入操作
450.删除二叉搜索树中的节点
感想
文档讲解:代码随想录
视频讲解:二叉搜索树找祖先就有点不一样了!| 235. 二叉搜索树的最近公共祖先_哔哩哔哩_bilibili
状态:上一期做了普通二叉树的,这道题怎么用上二叉搜索树的特性呢?先把普通二叉树的解法默写了一遍,在二叉搜索树也适用。要判断大小?判断了又怎么了呢?
看了题解,完全跳出了上一道题目。利用搜索二叉树有序的特点,从上向下遍历,当节点置于[p q]区间内时,它就是公共祖先,而且还是最近公共祖先(举例表明的)。节点5是p和q的最近公共祖先。
递归遍历顺序,本题没有中节点的处理逻辑,不涉及到 前中后序了,遍历顺序无所谓了。
当当前节点的值大于p q时,往左子树去遍历;当节点小于p q时,往右子树去遍历(目标区间在右子树)。
不需要遍历整棵树,找到结果直接返回!
本题就是标准的搜索一条边的写法,遇到递归函数的返回值,如果不为空,立刻返回。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if not root:
return None
if root.val > p.val and root.val > q.val: # 不知道p和q谁大,所以两个都要判断
left = self.lowestCommonAncestor(root.left,p,q)
if left:
return left
elif root.val < p.val and root.val < q.val:
right = self.lowestCommonAncestor(root.right,p,q)
if right:
return right
else:
return root # root在p q之间
二叉搜索树的迭代法可能都比较简单,因为其有序性,可以给我们提供方向性
class Solution:
def lowestCommonAncestor(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:
return root
return None
文档讲解:代码随想录
视频讲解:原来这么简单? | LeetCode:701.二叉搜索树中的插入操作_哔哩哔哩_bilibili
状态:提示说简单,但没感觉到很简单啊~~
用迭代法的话,不知道怎么让root回到最初 以返回根节点;
用递归法的话,提交出错了,因为都考虑成是叶子节点然后再在后面添加一个节点了,还有比如说根节点但其左子节点(左子树)为空,需要在此处添加的情况。
修改之后又出错了,当原本是一个空树时,没能成功往其中添加节点,但我明明写了if not root: return TreeNode(val),这使得我发现我没有正确处理递归函数是否有返回值的问题。
三类情况,感觉这题不太好归类。我写的是没有返回值,但在终止条件中写了if not root: return TreeNode(val) 又没有发挥作用,所以还是有返回值的,否则终止条件中是单纯的return就可以了。我应该是没有在递归函数中接住返回值,导致这个语句没有发挥作用?不知道怎么改,都改成没有返回值的也不对。
我比照测试用例,在脑子中一步步执行程序,发现执行的也不顺当,不太合理。
递归法尝试:
class Solution:
def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
self.traversal(root,val)
return root
def traversal(self,root,val):
if not root:
return TreeNode(val)
if root.val > val and not root.left:
root.left = TreeNode(val)
elif root.val < val and not root.right:
root.right = TreeNode(val)
if root.val > val:
self.traversal(root.left,val)
if root.val < val:
self.traversal(root.right,val)
递归法正确:
通过递归函数返回值完成新加入节点的父子关系赋值操作!
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
if not root: # 找到遍历的节点为null的时候,就是要插入节点的位置了
return TreeNode(val) # 把插入的节点返回
if root.val > val: # 利用二叉搜索树的有序性
root.left = self.insertIntoBST(root.left,val)
if root.val < val:
root.right = self.insertIntoBST(root.right,val)
# 下一层将加入节点返回,本层用root->left或者root->right将其接住
return root
相比于我写的错误方法,正确方法 进行了许多的合并,首先有返回值不用另写一个函数了;not root就是判断为空,不用not root.left或者not root.right了;root.val 和val的比较也重复了,用root.left root.right接住递归的返回值,返回值也就是not root时返回的TreeNode(val)。
我的思考:
所以由于二叉搜索树的特殊性,并不用完整遍历二叉树,但递归函数也有返回值。比较特殊,和那三类情况不太一样。
迭代法尝试:
class Solution:
def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
if not root:
return TreeNode(val)
while root:
if root.val > val:
root = root.left
elif root.val < val:
root = root.right
root = TreeNode(val) # 此时root来到了NULL的位置,也就是应该放置新节点的位置,我进行了赋值
# 但不知道怎么返回整棵树的根节点了,而且忽略了节点要插入,这并没有将新节点和二叉树连接起来
迭代法正确:
也需要用双指针!需要记录一下当前遍历的节点的父节点,才能进行插入节点的操作。取指针cur parent 进行移动,避免改变root,方便最终的返回。
class Solution:
def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
if not root: # 二叉树为空
return TreeNode(val)
cur = root
parent = root
while cur:
parent = cur # 记录 更新 当前节点的父节点
if cur.val > val:
cur = cur.left
elif cur.val < val:
cur = cur.right
# cur此时指向NULL
if parent.val > val:
parent.left = TreeNode(val) # 将新节点连接到父节点的左子树
elif parent.val < val:
parent.right = TreeNode(val) # 将新节点连接到父节点的右子树
return root
文档讲解:代码随想录
视频讲解:调整二叉树的结构最难!| LeetCode:450.删除二叉搜索树中的节点_哔哩哔哩_bilibili
状态:上一题只是单纯的插入节点,一定可以在叶子节点的位置找到要添加节点的位置,这一题还涉及到更改树的结构。
不知道删除的操作怎么进行,我在处理节点的左右皆不为空的情况时,没有妥善处理节点的左子树。删除节点的操作确实是放在了终止条件里这是对的!五种情况我也都想到了!
通过递归返回值删除节点
# Definition for a binary tree node.
# 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 None
if root.val == key:
if not root.left and not root.right: # 删的节点是叶子节点
return None # 不是root = None 而是return None
elif not root.left and root.right: # 删的节点左为空 右不空
return root.right
elif not root.right and root.left: # 删的节点右为空 左不空
return root.left # 不是root = root.left 而是return root.left 返回给root父节点的,作为其左子树
else: # 左右皆不为空
cur = root.right # cur指向当前节点root的右子树的根节点 也就是root的右节点
while cur.left:
cur = cur.left # 找到 右子树最左侧的节点,因为它是比左子树值大一点点的
cur.left = root.left # 将root的左子树放在 右子树最左侧节点的下面 即可保持搜索二叉树性质的不变
return root.right # 右子树继位 删除root
# 二叉搜索树的有序性 可以规划搜索方向
if root.val > key:
root.left = self.deleteNode(root.left,key) # 相当于把新的节点返回给上一层,上一层用root.left 接住返回的 节点
if root.val < key:
root.right = self.deleteNode(root.right,key)
return root
进阶:
普通二叉树删除节点怎么做?
学习用时:下午2.5h+晚上3h