写代码的第十七天
递归还是带点懵逼的
一定要记得有些题递归要向上有返回值的!切记!
我发现我写代码只要分的情况超过三个我就开始懵了,就不知道怎么写了。。。。
235. 二叉搜索树的最近公共祖先
思路(递归)
之前的那道题是二叉树的最近公共祖先,这个是二叉搜索树的最近公共祖先,所以要利用上二叉搜索树的性质。
从跟结点开始遍历,如果这个值在p和q之间那就证明这个结点是二叉搜索树中p和q的最近公共祖先(他不会是次近,因为二叉搜索树的性质,跟结点左边的值一定小于跟结点,右边的值一定大于跟结点,所以不可能存在一个结点在p和q之间但又不是最大公共祖先,画个图试试就明白了),如果这个值小于p和q那就证明pq的最近公共祖先在这个结点的右侧,如果这个值大于p和q那么就证明pq的最近公共祖先在这个结点的左侧。
解决问题1:递归参数和返回值是什么?参数包括传入的结点,最开始肯定是跟结点,以及p和q,返回值要根据题意返回最近公共祖先。
解决问题2:终止条件是什么?当出现一个结点的值在p和q之间,终止并且返回这个结点的值。
解决问题3:单层递归逻辑是什么?如果遍历结点的值小于p和q那就继续向右递归,如果这个值大于p和q那么就继续向左递归。
错误第一版:测试用例没通过。看了一下这个测试用例,发现出问题的地方在于有一种情况没有考虑到,当p为q的公共祖先或者q为p的公共祖先这种情况没有考虑到。并且没有在递归调用的时候将结果返回,所以上层递归是不知道本次递归的结果的。
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if (root.val > p.val and root.val < q.val) or (root.val < p.val and root.val > q.val):
return root
if root.val > p.val and root.val > q.val:
left = self.lowestCommonAncestor(root.left,p,q)
if root.val < p.val and root.val < q.val:
right = self.lowestCommonAncestor(root.right,p,q)
正确代码:在这里加了一个判断条件,为了解决p为q的父结点或者q为p的父结点这种情况。
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if (root.val > p.val and root.val < q.val) or (root.val < p.val and root.val > q.val):
return root
if root.val > p.val and root.val > q.val:
left = self.lowestCommonAncestor(root.left,p,q)
return left
if root.val < p.val and root.val < q.val:
right = self.lowestCommonAncestor(root.right,p,q)
return right
if root.val == p.val or root.val == q.val:
return root
思路(迭代)
超级简单,从跟结点开始遍历,如果pq大于跟结点那么指针向右遍历,如果pq小于 跟结点那么指针向左遍历,否则返回当前子树的根结点。
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
701.二叉搜索树中的插入操作
思路(递归)
在二叉搜索树中插入任何一个结点都可以在叶子结点中找到他的位置。按照二叉搜索树的性质遍历找到符合位置的空结点然后插入结点。
解决问题1:递归参数和返回值是什么?参数包括传入的结点,以及要插入的值,返回值要根据题意返回新的根结点。
解决问题2:终止条件是什么?当遇到了空结点,就把新结点插入进去(这里可能会有疑问,为什么遇到空的就插入,万一不是呢,我们根据后面的单层递归逻辑一层一层的递归,到最后遇到的空结点一定是符合条件的)。
解决问题3:单层递归逻辑是什么?如果val是大于当前结点的值的那么递归右子树,如果小于那么递归左子树。
错误第一版:结点根本没插入进去,也就是没和父结点连接上。也就是在递归的时候没向上一层返回值。
class Solution:
def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
if root == None:
node = TreeNode(val)
return node
if val > root.val:
right = self.insertIntoBST(root.right,val)
if val < root.val:
left = self.insertIntoBST(root.left,val)
return root
正确代码:
class Solution:
def insertIntoBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
if root == None:
node = TreeNode(val)
return node
if val > root.val:
root.right = self.insertIntoBST(root.right,val)
if val < root.val:
root.left = self.insertIntoBST(root.left,val)
return root
450.删除二叉搜索树中的节点
思路(递归)
自己是一点思路没有,一片混乱,感觉好多种情况根本写不完,看了卡哥的视频分五种情况解决,啊舒服,太舒服了。
情况1:删除的结点就是叶子结点(左右孩子都为空),那就直接删除,把其父结点的左孩子或右孩子指针之间变空即可。
情况2:删除的结点左孩子不空右孩子为空,那么将其父结点的left直接指向被删除结点的左孩子即可(就很像链表删除)。
情况3:删除的结点左孩子为空右孩子不空,那么将其父结点的right直接指向被删除结点的右孩子即可。
情况4:删除的结点左右孩子都不为空(这种最复杂,画个图其实大家就明明白了),那么将被删除结点的左子树(左边全部的结点按顺序)放到被删除结点的右子树的最左侧的叶子结点的left。(我们可以想一下,左右都不为空的结点,在二叉搜索树中左一定小于中一定小于右,所以中结点被删,左一定比右侧所有的结点都小,那右侧最小的结点一定在其最左侧最下面的叶子结点上,所以左一定要赋给这个结点的左子树,这样才保证更新完的树还是一个二叉搜索树)。
情况5:如果找不到被删除的结点,那么树结构不变。
解决问题1:参数和返回值是什么?参数是root以及要删除的结点的值,返回值是删除之后新的root。
解决问题2:终止条件是什么?按照上面说的五种情况写五个终止条件。
1️⃣如果root==None,直接return None。还是要解释一下这个地方,这是个递归的终止条件,相当于是最后一次递归,这个值是要返回给上一层的,不是说最后的输出结果就是None了。
2️⃣如果root.left!=None and root.right= =None,只需要上面说的指针改变的操作,所以直接返回给上一层相应的值。这里的返回值就是root.left。因为root是当前要删除的结点,他要返回给上一层递归的就是把他的左孩子赋值给其父结点的左孩子,所以返回值就是他自己的左孩子。
3️⃣同上,返回他自己的右孩子。
4️⃣如果root.left!=None and root.right!=None,这里要做的首先是找到该结点的右子树的最左侧的叶子结点(用一个while循环),然后将该结点的左子树放在这个结点的左孩子上,最后将这个结点的右孩子作为其父结点的右孩子。
解决问题3:单次递归逻辑是什么?如果val值大于root.val,那么向右侧递归;如果val值小于root.val,那么向左侧递归。
错误第一版:测试用例除了空的全没通过啊哈哈哈哈我疯啦。。。。都是缺少结点…啊啊啊啊啊啊啊啊只有在这个树中能找到值的情况下才能进行五种情况的判断啊啊啊啊!!!!
class Solution:
def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
if root == None:
return None
if root.left != None and root.right == None:
return root.left
if root.right != None and root.left == None:
return root.right
if root.left != None and root.right != None:
cur = root
while cur != None:
cur = cur.left
cur = root.left
return root.right
if val > root.val:
root.right = self.deleteNode(root.right,val)
if val < root.val:
root.left = self.deleteNode(root.left,val)
return root
错误第二版:第一个测试用例没通过。有一个结点变成null了。啊啊啊啊啊左右结点都是空的情况没写,我真服了啊啊啊啊。试了一下还是同样的错误,再想想奥。涉及到稍微复杂点的就是左右都不空的情况,检查一下这块吧。
错大发了:我们要找的是将要删除的结点的右子树中最左侧的叶子结点,所以cur指针应该初始化为将要删除的结点的右子树中!!!!我们一直要判断的是cur的left是不是空,一旦是空那么while循环停止,所以循环条件也错了!!!!
class Solution:
def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
if root == None:
return None
if root.val == key:
if root.left != None and root.right == None:
return root.left
if root.right != None and root.left == None:
return root.right
if root.left != None and root.right != None:
cur = root
while cur != None:
cur = cur.left
cur = root.left
return root.right
if key > root.val:
root.right = self.deleteNode(root.right,key)
if key < root.val:
root.left = self.deleteNode(root.left,key)
return root
正确代码:
class Solution:
def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
if root == None:
return None
if root.val == key:
if root.left != None and root.right == None:
return root.left
if root.right != None and root.left == None:
return root.right
if root.left != None and root.right != None:
cur = root.right
while cur.left != None:
cur = cur.left
cur.left = root.left
return root.right
if root.left == None and root.right == None:
return None
if key > root.val:
root.right = self.deleteNode(root.right,key)
if key < root.val:
root.left = self.deleteNode(root.left,key)
return root
总结
递归真的要命,真救了命了。。。。。。
好多点其实还是有点模糊,我估计再做几道应该能扫清一些障碍。