题目 530.二叉搜索树的最小绝对差
问题描述
给定一个所有节点为非负值的二叉搜索树,求树中任意两节点的差的绝对值的最小值。
解题思路
- 我们需要了解二叉搜索树(BST)的一个关键特性:对 BST 进行中序遍历将得到一个递增的序列。这意味着最小的绝对差将存在于两个相邻的元素之间。
- 由于最小的绝对差在相邻的元素之间,我们在进行中序遍历的同时,比较当前节点和前一个节点的差值,更新最小差值。
- 我们使用一个全局变量
prev
来记录前一个节点,min_diff
来记录当前的最小差值。在遍历的过程中,我们更新这两个变量。
代码
- 定义二叉树节点和初始化全局变量
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def __init__(self):
self.prev = -1
self.min_diff = float('inf')
- 中序遍历和更新最小差值
def getMinimumDifference(self, root: Optional[TreeNode]) -> int:
if root is None:
return self.min_diff
self.getMinimumDifference(root.left)
if self.prev != -1:
self.min_diff = min(self.min_diff, root.val - self.prev)
self.prev = root.val
self.getMinimumDifference(root.right)
return self.min_diff
- 利用中序递增,结合数组
class Solution:
def __init__(self):
self.vec = []
def traversal(self, root):
if root is None:
return
self.traversal(root.left)
self.vec.append(root.val) # 将二叉搜索树转换为有序数组
self.traversal(root.right)
def getMinimumDifference(self, root):
self.vec = []
self.traversal(root)
if len(self.vec) < 2:
return 0
result = float('inf')
for i in range(1, len(self.vec)):
# 统计有序数组的最小差值
result = min(result, self.vec[i] - self.vec[i - 1])
return result
- 迭代法
class Solution:
def getMinimumDifference(self, root):
stack = []
cur = root
pre = None
result = float('inf')
while cur is not None or len(stack) > 0:
if cur is not None:
stack.append(cur) # 将访问的节点放进栈
cur = cur.left # 左
else:
cur = stack.pop()
if pre is not None: # 中
result = min(result, cur.val - pre.val)
pre = cur
cur = cur.right # 右
return result
复杂度分析(递归)
也就是递归栈的深度。在最坏的情况下,树可能完全倾斜,递归深度等于树的节点数量。
题目 501.二叉搜索树中的众数
问题描述
给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。
假定 BST 有如下定义:
结点左子树中所含结点的值小于等于当前结点的值
结点右子树中所含结点的值大于等于当前结点的值
左子树和右子树都是二叉搜索树
解题思路
- 中序遍历:使用中序遍历的方式遍历二叉搜索树。首先遍历左子树,然后访问当前节点,最后遍历右子树。
- 使用全局变量:为了在递归过程中共享状态,使用全局变量来记录上一个节点的值和当前的最小差值。
- 初始化变量:在开始遍历之前,初始化上一个节点的值为 None,并将最小差值设置为一个较大的初始值(如正无穷)。
- 更新最小差值:对于当前节点,计算它与上一个节点值的差值,并将其与当前最小差值进行比较。如果小于当前最小差值,则更新最小差值。
- 递归遍历左右子树:在递归调用中,按照中序遍历的顺序遍历左子树和右子树。
代码
- 递归法(版本一)利用字典
from collections import defaultdict
class Solution:
def searchBST(self, cur, freq_map):
if cur is None:
return
freq_map[cur.val] += 1 # 统计元素频率
self.searchBST(cur.left, freq_map)
self.searchBST(cur.right, freq_map)
def findMode(self, root):
freq_map = defaultdict(int) # key:元素,value:出现频率
result = []
if root is None:
return result
self.searchBST(root, freq_map)
max_freq = max(freq_map.values())
for key, freq in freq_map.items():
if freq == max_freq:
result.append(key)
return result
- 递归法(版本二)利用二叉搜索树性质
class Solution:
def __init__(self):
self.maxCount = 0 # 最大频率
self.count = 0 # 统计频率
self.pre = None
self.result = []
def searchBST(self, cur):
if cur is None:
return
self.searchBST(cur.left) # 左
# 中
if self.pre is None: # 第一个节点
self.count = 1
elif self.pre.val == cur.val: # 与前一个节点数值相同
self.count += 1
else: # 与前一个节点数值不同
self.count = 1
self.pre = cur # 更新上一个节点
if self.count == self.maxCount: # 如果与最大值频率相同,放进result中
self.result.append(cur.val)
if self.count > self.maxCount: # 如果计数大于最大值频率
self.maxCount = self.count # 更新最大频率
self.result = [cur.val] # 很关键的一步,不要忘记清空result,之前result里的元素都失效了
self.searchBST(cur.right) # 右
return
def findMode(self, root):
self.count = 0
self.maxCount = 0
self.pre = None # 记录前一个节点
self.result = []
self.searchBST(root)
return self.result
- 迭代法
class Solution:
def findMode(self, root):
st = []
cur = root
pre = None
maxCount = 0 # 最大频率
count = 0 # 统计频率
result = []
while cur is not None or st:
if cur is not None: # 指针来访问节点,访问到最底层
st.append(cur) # 将访问的节点放进栈
cur = cur.left # 左
else:
cur = st.pop()
if pre is None: # 第一个节点
count = 1
elif pre.val == cur.val: # 与前一个节点数值相同
count += 1
else: # 与前一个节点数值不同
count = 1
if count == maxCount: # 如果和最大值相同,放进result中
result.append(cur.val)
if count > maxCount: # 如果计数大于最大值频率
maxCount = count # 更新最大频率
result = [cur.val] # 很关键的一步,不要忘记清空result,之前result里的元素都失效了
pre = cur
cur = cur.right # 右
return result
复杂度分析
题目 236. 二叉树的最近公共祖先
问题描述
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
解题思路
- 从根节点开始搜索。
- 如果根节点为空,或者根节点就是要查找的其
中一个节点,我们返回根节点。 - 我们对左子树和右子树进行递归搜索。如果左右两侧都找到了,那么根节点就是最近公共祖先。
- 如果左侧没有找到,那么最近公共祖先在右侧;反之亦然。
如果左右两侧都没找到,那么最近公共祖先不存在。
-遇到 q 或者 p 就返回,这样也包含了 q 或者 p 本身就是 公共祖先的情况。
注意!
- 求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从底向上的遍历方式。
- 在回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。
- 要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果。
递归过程
-
从根节点 3 开始执行
lowestCommonAncestor(3, 7, 4)
。- 检查节点 3 是否等于节点 7 或节点 4。这不是真的,所以我们需要继续递归。
-
在左子树中递归地找节点 7 和节点 4 的最近公共祖先,即执行
lowestCommonAncestor(5, 7, 4)
。- 在节点 5 的左子树中(即节点 6),我们没有找到节点 7 或节点 4,所以返回 None。
- 在节点 5 的右子树中(即节点 2),我们找到了节点 7 和节点 4,它们的最近公共祖先是节点 2,所以返回节点 2。
- 因此,
lowestCommonAncestor(5, 7, 4)
在左子树中返回 None,右子树中返回节点 2,所以最终返回节点 2。
-
回到
lowestCommonAncestor(3, 7, 4)
,我们在左子树中找到了最近公共祖先,即节点 2。 -
接着,我们在右子树中找节点 7 和节点 4 的最近公共祖先,即执行
lowestCommonAncestor(1, 7, 4)
。- 在节点 1 的左子树(即节点 0)和右子树(即节点 8)中,我们都没有找到节点 7 或节点 4,所以都返回 None。
- 因此,
lowestCommonAncestor(1, 7, 4)
在左子树和右子树中都返回 None,所以最终返回 None。
-
最后,在
lowestCommonAncestor(3, 7, 4)
中,左子树的返回值是节点 2,右子树的返回值是 None,所以返回左子树的返回值,即节点 2。
所以,节点 7 和节点 4 的最近公共祖先是节点 2。
代码
- 递归
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
# 如果根节点为空,返回None
if root is None:
return None
# 如果根节点等于p或q,说明根节点是它们的公共祖先
if root == p or root == q:
return root
# 在左子树中寻找p和q的最近公共祖先
left = self.lowestCommonAncestor(root.left, p, q)
# 在右子树中寻找p和q的最近公共祖先
right = self.lowestCommonAncestor(root.right, p, q)
# 如果p和q分别在根节点的左右子树中,那么根节点就是它们的最近公共祖先
if left is not None and right is not None:
return root
# 如果只有右子树中找到了公共祖先,返回右子树中的公共祖先
elif left is None and right is not None:
return right
# 如果只有左子树中找到了公共祖先,返回左子树中的公共祖先
elif left is not None and right is None:
return left
# 如果左右子树中都没有找到公共祖先,返回None
else:
return 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:
return right
return left