235. 二叉搜索树的最近公共祖先
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
示例 1:
- 输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
- 输出: 6
- 解释: 节点 2 和节点 8 的最近公共祖先是 6。
示例 2:
- 输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
- 输出: 2
- 解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
说明:
- 所有节点的值都是唯一的。
- p、q 为不同节点且均存在于给定的二叉搜索树中。
思路:
一开始只能想到普通的二叉树搜索方法套用过来,主要是没能想通从根节点往下搜索,第一次遍历到的处于两点值之间的节点值就是最近公共祖先。以下资料助于理解:
路径唯一性:
-
- 在BST中,从根节点到任何节点的路径是唯一的。这意味着任何两个节点的路径在某个点上会分叉,这个分叉点就是它们的最近公共祖先。
不可能再低:
-
- 如果你继续向下遍历,无论是向左还是向右,你都会离开其中一个节点的路径,因此不可能找到更低的公共祖先。
代码实现如下:
# 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
cur = root
while cur:
if cur.val < p.val and cur.val < q.val:
cur = cur.right
elif cur. val > p.val and cur.val > q.val:
cur = cur.left
else:
return cur
return 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)
迭代法:
class Solution:
def lowestCommonAncestor(self, root, p, q):
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
701.二叉搜索树中的插入操作
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据保证,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回任意有效的结果。
提示:
- 给定的树上的节点数介于 0 和 10^4 之间
- 每个节点都有一个唯一整数值,取值范围从 0 到 10^8
- -10^8 <= val <= 10^8
- 新值和原始二叉搜索树中的任意节点值都不同
思路:
使用二叉搜索树的特性,搜索该节点应该在的位置(事实上搜索树中没有这个节点),在遍历到叶子节点后,直接将插入节点插入在该叶子节点下作为新的叶子节点即可。
# 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:
root = TreeNode(val)
return root
pre = None
cur = root
while cur:
pre = cur
if val > cur.val:
cur = cur.right
elif val < cur.val:
cur = cur.left
if val > pre.val:
pre.right = TreeNode(val)
else:
pre.left = TreeNode(val)
return root
规范代码:
递归法:
class Solution:
def __init__(self):
self.parent = None
def traversal(self, cur, val):
if cur is None:
node = TreeNode(val)
if val > self.parent.val:
self.parent.right = node
else:
self.parent.left = node
return
self.parent = cur
if cur.val > val:
self.traversal(cur.left, val)
if cur.val < val:
self.traversal(cur.right, val)
def insertIntoBST(self, root, val):
self.parent = TreeNode(0)
if root is None:
return TreeNode(val)
self.traversal(root, val)
return root
迭代法:
class Solution:
def insertIntoBST(self, root, val):
if root is None: # 如果根节点为空,创建新节点作为根节点并返回
node = TreeNode(val)
return node
cur = root
parent = root # 记录上一个节点,用于连接新节点
while cur is not None:
parent = cur
if cur.val > val:
cur = cur.left
else:
cur = cur.right
node = TreeNode(val)
if val < parent.val:
parent.left = node # 将新节点连接到父节点的左子树
else:
parent.right = node # 将新节点连接到父节点的右子树
return root
450.删除二叉搜索树中的节点
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点; 如果找到了,删除它。 说明: 要求算法时间复杂度为 $O(h)$,h 为树的高度。
示例:
思路:
删除需要分多种情况:
- 找不到该节点,直接返回
- 删除的是根节点:
- 右子树存在:直接将左子树作为这个【右子树的最小节点】的左子树即可
- 右子树不存在:直接将左子树作为新的树
- 找到对应的节点且不是根节点:以该节点为根节点的子树,对这棵子树执行以上第二步删除根节点的操作,使得子树变为返回的新子树。
代码实现如下:
# 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
pre = None
cur = root
while cur:
if key > cur.val:
pre = cur
cur = cur.right
elif key < cur.val:
pre = cur
cur = cur.left
else:
if not pre: # 删除根节点的情况
if cur.right:
newroot = cur.right
while newroot.left:
newroot = newroot.left
#root.val = newroot.val
#newroot = newroot.right
#return root
#原本的做法是想将右子树的最小节点作为新的根节点,然后将该节点删除,发现过程要另外考虑的东西多很多,看了解析后豁然开朗,直接将左子树作为这个【右子树的最小节点】的左子树即可
newroot.left = root.left
return root.right
else:
return root.left
else: # 删除其他节点的情况
#cur = self.deleteNode(cur, key)
if cur.val < pre.val:
pre.left = self.deleteNode(cur, key)
else:
pre.right = self.deleteNode(cur, key)
break
return root
规范代码:
递归法:
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
迭代法:
class Solution:
def deleteOneNode(self, target: TreeNode) -> TreeNode:
"""
将目标节点(删除节点)的左子树放到目标节点的右子树的最左面节点的左孩子位置上
并返回目标节点右孩子为新的根节点
是动画里模拟的过程
"""
if target is None:
return target
if target.right is None:
return target.left
cur = target.right
while cur.left:
cur = cur.left
cur.left = target.left
return target.right
def deleteNode(self, root: TreeNode, key: int) -> TreeNode:
if root is None:
return root
cur = root
pre = None # 记录cur的父节点,用来删除cur
while cur:
if cur.val == key:
break
pre = cur
if cur.val > key:
cur = cur.left
else:
cur = cur.right
if pre is None: # 如果搜索树只有头结点
return self.deleteOneNode(cur)
# pre 要知道是删左孩子还是右孩子
if pre.left and pre.left.val == key:
pre.left = self.deleteOneNode(cur)
if pre.right and pre.right.val == key:
pre.right = self.deleteOneNode(cur)
return root