二叉搜索树
一、二叉搜索树的属性
遇到二叉搜索树,立刻想到这句话:「二叉搜索树(BST)的中序遍历是有序的」。这是解决所有二叉搜索树问题的关键。
98-验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
- 节点的左子树只包含小于当前节点的数。
- 节点的右子树只包含大于当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:
2
/ \
1 3
输出: true
示例 2:
输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4 。
递归方法:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
def dfs(node, min_val, max_val):
if not node: # 边界条件,如果node为空肯定是二叉搜索树
return True
if not min_val < node.val < max_val: # 如果当前节点超出上下界范围,肯定不是
return False
# 走到下面这步说明已经满足了如题所述的二叉搜索树的前两个条件
# 那么只需要递归判断当前节点的左右子树是否同时是二叉搜索树即可
return dfs(node.left, min_val, node.val) and dfs(node.right, node.val, max_val)
result = dfs(root, float('-inf'), float('inf')) # 对于根节点,它的上下限为无穷大和无穷小
return result
中序遍历:
- 思路简单:将中序遍历的值一个个存入一个list中。理解二叉搜索树后,明白二叉搜索树中序遍历后是个递增序列。最后判断list是否为一个递增序列即可。
# 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 isValidBST(self, root: TreeNode) -> bool:
inorder = []
# 中序遍历
def bfs(node):
if node is None:
return
if node.left is not None:
bfs(node.left)
inorder.append(node.val)
if node.right is not None:
bfs(node.right)
bfs(root)
print("inorder = ", inorder)
for i in range(1, len(inorder)):
if inorder[i] <= inorder[i - 1]:
return False
return True
# 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 isValidBST(self, root: TreeNode) -> bool:
inorder = []
# 中序遍历
def bfs(node):
if node is None:
return
if node.left is not None:
bfs(node.left)
inorder.append(node.val)
if node.right is not None:
bfs(node.right)
bfs(root)
print("inorder = ", inorder)
result = inorder == sorted(inorder) and len(set(inorder)) == len(inorder)
return result
700-二叉搜索树中的搜索
给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。
例如,
给定二叉搜索树:
4
/ \
2 7
/ \
1 3
和值: 2
你应该返回如下子树:
2
/ \
1 3
在上述示例中,如果要找的值是 5,但因为没有节点值为 5,我们应该返回 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 searchBST(self, root: TreeNode, val: int) -> TreeNode:
def dfs(node, val):
# 空节点,返回
if node is None:
return None
# 查找到 返回 node
if node.val == val:
return node
# node.val > val 在左子树中查找
if node.val > val:
return dfs(node.left, val)
# node.val < val 在右子树中查找
if node.val < val:
return dfs(node.right, val)
return dfs(root, val)
783-二叉搜索树节点最小距离【530-二叉搜索树的最小绝对差 】
给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
示例 1:
输入:root = [4,2,6,1,3]
输出:1
示例 2:
输入:root = [1,0,48,null,null,12,49]
输出:1
提示:
- 树中节点数目在范围 [2, 100] 内
- 0 < = N o d e . v a l < = 1 0 5 0 <= Node.val <= 10^5 0<=Node.val<=105
- 差值是一个正数,其数值等于两值之差的绝对值
方法:中序遍历
- 要求 BST 的任意两个不同节点之间的最小差值,也就是相当于求 BST 中序遍历得到的有序序列中所有相邻节点之间的最小差值。
# 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 minDiffInBST(self, root: TreeNode) -> int:
inorderList = []
def inorder(node):
if node is None:
return
if node.left is not None:
inorder(node.left)
inorderList.append(node.val)
if node.right is not None:
inorder(node.right)
inorder(root)
result = min([inorderList[i] - inorderList[i - 1] for i in range(1, len(inorderList))])
return result
501-二叉搜索树中的众数
给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。
假定 BST 有如下定义:
- 结点左子树中所含结点的值小于等于当前结点的值
- 结点右子树中所含结点的值大于等于当前结点的值
- 左子树和右子树都是二叉搜索树
例如:
给定 BST [1,null,2,2],
1
\
2
/
2
返回[2].
提示:如果众数超过1个,不需考虑输出顺序
进阶:你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内)
方法一:利用字典
# 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 findMode(self, root: TreeNode) -> List[int]:
dict = {}
def preorder(node):
if node is None:
return
dict[node.val] = dict.get(node.val, 0) + 1
if node.left is not None:
preorder(node.left)
if node.right is not None:
preorder(node.right)
preorder(root)
print("dict = ", dict)
mode = max(dict.values())
print("mode = ", mode)
result = []
for key in dict.keys():
if dict[key] == mode:
result.append(key)
return result
二、二叉搜索树的修改与构造
108-将有序数组转换为二叉搜索树
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
示例 1:
输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:
示例 2:
输入:nums = [1,3]
输出:[3,1]
解释:[1,3] 和 [3,1] 都是高度平衡二叉搜索树。
提示:
- 1 < = n u m s . l e n g t h < = 1 0 4 1 <= nums.length <= 10^4 1<=nums.length<=104
- − 1 0 4 < = n u m s [ i ] < = 1 0 4 -10^4 <= nums[i] <= 10^4 −104<=nums[i]<=104
- nums 按 严格递增 顺序排列
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
def make_tree(start_index, end_index): #只和长度有关
#首先判定我们的区间是否合理,即left_index要<=right_index
#当相等时,只有root会产生,不会产生左右小树
if start_index > end_index:
return None
#我这里变量名都写得比较长,目的是方便理解
mid_index = (start_index + end_index)//2
this_tree_root = TreeNode(nums[mid_index]) #做一个小树的root
this_tree_root.left = make_tree(start_index,mid_index-1)
this_tree_root.right = make_tree(mid_index+1, end_index)
return this_tree_root #做好的小树
return make_tree(0,len(nums)-1)
#可以看到整个题解只和index有关,和数组里的具体数字无关,
#因为题目给出的“有序数列”帮助我们满足了“二叉搜索树”的条件。
538-把二叉搜索树转换为累加树
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
- 节点的左子树仅包含键 小于 节点键的节点。
- 节点的右子树仅包含键 大于 节点键的节点。
- 左右子树也必须是二叉搜索树。
注意:本题和 1038: https://leetcode-cn.com/problems/binary-search-tree-to-greater-sum-tree/ 相同
示例 1:
输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]
输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]
示例 2:
输入:root = [0,null,1]
输出:[1,null,1]
示例 3:
输入:root = [1,0,2]
输出:[3,3,2]
示例 4:
输入:root = [3,2,4,1]
输出:[7,9,4,10]
提示:
- 树中的节点数介于 0 和 1 0 4 10^4 104 之间。
- 每个节点的值介于 − 1 0 4 -10^4 −104 和 1 0 4 10^4 104 之间。
- 树中的所有值 互不相同 。
- 给定的树为二叉搜索树。
方法一:反中序遍历
本题中要求我们将每个节点的值修改为原来的节点值加上所有大于它的节点值之和。这样我们只需要反序中序遍历该二叉搜索树,记录过程中的节点值之和,并不断更新当前遍历到的节点的节点值,即可得到题目要求的累加树。
# 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:
total = 0
def convertBST(self, root: TreeNode) -> TreeNode:
def dfs(node: TreeNode):
if node is None:
return
if node.right is not None:
dfs(node.right)
self.total += node.val
node.val = self.total
if node.left is not None:
dfs(node.left)
dfs(root)
return root
701-二叉搜索树中的插入操作
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
示例 1:
输入:root = [4,2,7,1,3], val = 5
输出:[4,2,7,1,3,5]
解释:另一个满足题目要求可以通过的树是:
示例 2:
输入:root = [40,20,60,10,30,50,70], val = 25
输出:[40,20,60,10,30,50,70,null,null,25]
示例 3:
输入:root = [4,2,7,1,3,null,null,null,null,null,null], val = 5
输出:[4,2,7,1,3,5]
提示:
- 给定的树上的节点数介于 0 和 1 0 4 10^4 104 之间
- 每个节点都有一个唯一整数值,取值范围从 0 到 1 0 8 10^8 108
- − 1 0 8 < = v a l < = 1 0 8 -10^8 <= val <= 10^8 −108<=val<=108
- 新值和原始二叉搜索树中的任意节点值都不同
递归方法:
- 我们只需要确定val是要放在左子树还是右子树即可。递归结束条件是 node == None.
# 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: TreeNode, val: int) -> TreeNode:
def dfs(node: TreeNode, val:int)->TreeNode:
if node is None:
return TreeNode(val)
if val < node.val:
node.left = dfs(node.left, val)
if val > node.val:
node.right = dfs(node.right, val)
return node
return dfs(root, val)
450-删除二叉搜索树中的节点
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
- 首先找到需要删除的节点;
- 如果找到了,删除它。
说明: 要求算法时间复杂度为 O(h),h 为树的高度。
示例:
root = [5,3,6,2,4,null,7]
key = 3
5
/ \
3 6
/ \ \
2 4 7
给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。
5
/ \
4 6
/ \
2 7
另一个正确答案是 [5,2,6,null,4,null,7]。
5
/ \
2 6
\ \
4 7
# 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: TreeNode, key: int) -> TreeNode:
def getSuccessor(node):
"""获取node的后继节点"""
node = node.right # 定位到node右子树
while node.left: # 寻找右子树中最靠左的节点
node = node.left
return node
def dfs(node: TreeNode, key: int)->TreeNode:
"""
删除具有key值的节点,并返回删除后的根节点
"""
if not node:
return None
# 最外层的if...elif...else用于搜索待删除结点
if node.val > key: # 待删除结点值小于根节点,位于根节点左子树
node.left = dfs(node.left, key) # 递归删除左子树,并返回删除后的左子树
elif node.val < key: # 待删除结点值大于根节点,位于根节点右子树
node.right = dfs(node.right, key) # 递归删除右子树,并返回删除后的右子树
else: # 待删除节点为根节点
if node.left is None and node.right is None: # 对应情况1,待删除结点没有子节点
node = None # 将该节点置空
elif node.left is not None and node.right is None: # 对应情况2,待删除节点只有左节点
node = node.left # 用左节点代替该节点
elif node.left is None and node.right is not None: # 对应情况3,待删除节点只有右节点
node = node.right # 用右节点代替该节点
else: # 对应情况3,待删除节点有左右两个节点
succ = getSuccessor(node) # 找到后继节点
node.val = succ.val # 将值替换为后继节点的值
node.right = dfs(node.right, succ.val) # 删除没用的后继节点
return node
return dfs(root, key)
669-修剪二叉搜索树
给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树不应该改变保留在树中的元素的相对结构(即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在唯一的答案。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。
示例 1:
输入:root = [1,0,2], low = 1, high = 2
输出:[1,null,2]
示例 2:
输入:root = [3,0,4,null,2,null,null,1], low = 1, high = 3
输出:[3,2,null,1]
示例 3:
输入:root = [1], low = 1, high = 2
输出:[1]
示例 4:
输入:root = [1,null,2], low = 1, high = 3
输出:[1,null,2]
示例 5:
输入:root = [1,null,2], low = 2, high = 4
输出:[2]
提示:
- 树中节点数在范围 [ 1 , 1 0 4 ] [1, 10^4] [1,104] 内
- 0 < = N o d e . v a l < = 1 0 4 0 <= Node.val <= 10^4 0<=Node.val<=104
- 树中每个节点的值都是唯一的
- 题目数据保证输入是一棵有效的二叉搜索树
- 0 < = l o w < = h i g h < = 1 0 4 0 <= low <= high <= 10^4 0<=low<=high<=104
# 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:
def dfs(node):
if not node:
return None
if node.val < low: # 如果当前节点值小于左边界,则该节点左子树全小于左边界,应该被删除
node = dfs(node.right)
elif node.val > high: # 如果当前节点值大于右边界,则该节点右子树全大于右边界,应该被删除
node = dfs(node.left)
else: # 在界限中,则往左右子树搜
node.left = dfs(node.left)
node.right = dfs(node.right)
return node
return dfs(node)