文章目录
前言
今天带大家进行二叉树的实战篇4,带大家来看看二叉树的公共祖先的问题以及二叉修改树的构建与修改有什么细节,一文带大家弄懂。本文用于记录自己的学习过程,同时向大家进行分享相关的内容。本文内容参考于代码随想录同时包含了自己的许多学习思考过程,如果有错误的地方欢迎批评指正!
二叉树的公共祖先问题
二叉树的最近公共祖先
236. 二叉树的最近公共祖先 - 力扣(LeetCode)
相关技巧:怎么找公共祖先,是不是我们应该自底向上找,那怎么自底向上找呢?可以通过回溯的方式后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。接下来就看如何判断一个节点是节点q和节点p的公共祖先呢。如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。
- 确定递归的参数和返回值:需要递归函数返回值,来告诉我们是否找到节点q或者p,那么返回值为bool类型就可以了。但我们还要返回最近公共节点,可以利用上题目中返回值是TreeNode * ,那么如果遇到p或者q,就把q或者p返回,返回值不为空,就说明找到了q或者p。
- 确定递归的终止条件:遇到空的话,因为树都是空了,所以返回空或者我们找到了节点即返回。
- 确定单层递归的逻辑:如果左右边均不为空,说明其分布在左右子树,所以当前的根节点就是其最小公共祖先,如果都在左边,就返回左边,如果都在右边,就返回右边。
# 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, p, q):
if root == q or root == p or root is None:
return root
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if left is not None and right is not None:
return root
if left is None and right is not None:
return right
elif left is not None and right is None:
return left
else:
return None
二叉搜索树的最近公共祖先
235. 二叉搜索树的最近公共祖先 - 力扣(LeetCode)
相关技巧:这就比找二叉树的最近公共祖先简单许多,也更容易理解。我们来看,因为是二叉搜索树,所以满足性质左子树均小于根节点,右子树均大于根节点,那我们来看,当p,q均小于根节点的时候,说明其左子树一定会存在个更近的公共祖先,就进左子树递归在找,反之就进右子树递归在找,但若一个大一个小,就说明当前的根节点就是其最近的公共祖先了,直接返回当前节点即可。
- 确定递归的参数和返回值:当前的节点和需要比较的p,q值,返回即返回找到的最近公共祖先节点
- 确定递归的终止条件:当节点为空即返回None
- 确定单层递归的逻辑:当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 traversal(self, cur, p, q):
if cur is None:
return cur
# 中
if cur.val > p.val and cur.val > q.val: # 左
left = self.traversal(cur.left, p, q)
if left is not None:
return left
if cur.val < p.val and cur.val < q.val: # 右
right = self.traversal(cur.right, p, q)
if right is not None:
return right
return cur
def lowestCommonAncestor(self, root, p, q):
return self.traversal(root, p, q)
二叉搜索树的构造与修改
二叉搜索树中的插入操作
701. 二叉搜索树中的插入操作 - 力扣(LeetCode)
相关技巧:这道题比较简单,而且返回的形式可以很多,所以很容易处理,我们直接比较值当小于就进左子树,大于就进右子树,直到空节点,就直接插入即可
- 确定递归的参数和返回值:参数就是当前节点以及插入的值,返回整个树
- 确定递归的终止条件:当节点为空插入当前的值
- 确定单层递归的逻辑:
- 直接比较值当小于就进左子树,大于就进右子树,直到空节点,就直接插入即可
# 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:
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)
return root
删除二叉搜索树中的节点
450. 删除二叉搜索树中的节点 - 力扣(LeetCode)
相关技巧:删除节点的情况比较多,所以我们每个情况都应该考虑到。
- 确定递归的参数和返回值:当前的节点和删除的值
- 确定递归的终止条件:当节点为空返回none
- 确定单层递归的逻辑:这里就把二叉搜索树中删除节点遇到的情况都搞清楚。有以下五种情况:
- 第一种情况:没找到删除的节点,遍历到空节点直接返回了
- 找到删除的节点
- 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
- 第三种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
- 第四种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
- 第五种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。
# 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, key):
if root is None:
return root
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
cur.left = root.left
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
修剪二叉搜索树
相关技巧:看着如果区间存在于左右子树该怎么处理好像没有头绪,感觉有些困难,但其实还是比较容易的,当我们找到low边界时,无论他在哪,那么low边界的左子树肯定不符合条件,所以我们可以直接令low父节点的左孩子为low的右孩子即可。同样的high边界,其右子树肯定不符合条件,我们可以直接令high父节点的右孩子为high的右孩子即可
- 确定递归的参数和返回值:经过分析传入参数为节点,low,high,返回值即为处理过后的二叉树
- 确定递归的终止条件:当节点为空的时候返回none
- 确定单层递归的逻辑:比较当前的值与low和high的,如果root(当前节点)的元素小于low的数值,那么应该递归右子树,并返回右子树符合条件的头结点。如果root(当前节点)的元素大于high的,那么应该递归左子树,并返回左子树符合条件的头结点。接下来要将下一层处理完左子树的结果赋给root->left,处理完右子树的结果赋给root->right。
# 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 trimBST(self, root: TreeNode, low: int, high: int) -> TreeNode:
if root is None:
return None
if root.val < low:
# 寻找符合区间 [low, high] 的节点
return self.trimBST(root.right, low, high)
if root.val > high:
# 寻找符合区间 [low, high] 的节点
return self.trimBST(root.left, low, high)
root.left = self.trimBST(root.left, low, high) # root.left 接入符合条件的左孩子
root.right = self.trimBST(root.right, low, high) # root.right 接入符合条件的右孩子
return root
将有序数组转换为二叉搜索树
108. 将有序数组转换为二叉搜索树 - 力扣(LeetCode)
相关技巧:这还是很简单的,我们通过寻找根节点,然后递归来构造左右子树,小于的在左子树,大于的在右子树。怎么找最大的,因为是有序的我们直接可套用二分法即可去做,注意就是边界的处理,循环不变性。
-
确定递归的参数和返回值:我们需要传入数组,左边界和右边界,最终返回构建的树
-
确定递归的终止条件:当left>right的时候即遍历完成终止。
-
确定单层递归的逻辑:取中间的值为根节点,左边的作为左子树重新构建,右边的作为右子树直接构建。然后递归处理即可
# 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 traversal(self, nums: List[int], left: int, right: int) -> TreeNode:
if left > right:
return None
mid = left + (right - left) // 2
root = TreeNode(nums[mid])
root.left = self.traversal(nums, left, mid - 1)
root.right = self.traversal(nums, mid + 1, right)
return root
def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
root = self.traversal(nums, 0, len(nums) - 1)
return root
算法基础系列
一文了解什么是数组及其经典考察题目
走进链表及其经典考察题目
还不知道什么是哈希表,看这篇文章就够了
字符串匹配究极大招【KMP】:带你一步步从原理到构建
【栈与队列】:基础实战篇-CSDN博客
【双指针法】:这么常用的你怎么能不知道-CSDN博客
【二叉树】理论基础篇1-CSDN博客
【二叉树】实战篇1-CSDN博客
【二叉树】实战篇2-CSDN博客
【二叉树】实战篇3-CSDN博客