二叉搜索树(BST)作为一种基础数据结构,在算法面试和实际工程中都有着广泛应用。这道LeetCode中等难度题目要求我们在BST中找到第k小的元素,看似简单却暗藏玄机。不同于普通二叉树,BST特有的左小右大性质为解题提供了优化空间。
在实际开发中,类似需求并不少见。比如电商平台需要实时统计销量第k高的商品,社交网络要找出关注数第k小的用户。这类问题的共同特点是:数据动态变化且需要频繁查询排序后的特定位置元素。理解BST的排序特性,就能高效解决这类问题。
BST的中序遍历(左-根-右)会产生一个升序序列,这是该解法的基础。通过递归遍历,我们可以按顺序访问所有节点,当访问到第k个元素时立即返回。这种方法时间复杂度O(H+k),其中H是树高,空间复杂度O(H)来自递归栈。
python复制class Solution:
def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
self.count = 0
self.result = None
def inorder(node):
if not node or self.result is not None:
return
inorder(node.left)
self.count += 1
if self.count == k:
self.result = node.val
return
inorder(node.right)
inorder(root)
return self.result
使用类变量count和result可以避免在递归中传递多个参数。当找到结果后,通过检查result是否为None来提前终止不必要的递归。这种剪枝优化在k值较小时效果显著。
注意:在Python中,整数是不可变对象,如果直接在递归中传递count变量,内层递归的修改不会反映到外层。这就是我们使用类变量的原因。
递归解法虽然简洁,但在实际工程中可能存在栈溢出风险。迭代解法使用显式栈模拟递归过程,更适合处理大规模数据。其时间复杂度同样为O(H+k),但空间复杂度可以优化到O(H)。
python复制def kthSmallest(root, k):
stack = []
while True:
while root:
stack.append(root)
root = root.left
root = stack.pop()
k -= 1
if k == 0:
return root.val
root = root.right
这种实现方式更符合工程实践要求,特别是在需要支持遍历暂停和恢复的场景下。
| 场景 | 递归解法 | 迭代解法 |
|---|---|---|
| 最佳情况(k=1) | O(H) | O(H) |
| 平均情况 | O(H+k) | O(H+k) |
| 最坏情况(链表) | O(n) | O(n) |
递归解法受限于调用栈深度,对于极度不平衡的树(退化成链表),空间复杂度会达到O(n)。迭代解法通过手动管理栈,在某些语言实现中可以更精确控制内存使用。
如果需要在同一棵BST上多次查询不同k值,简单的遍历方法效率不高。此时可以考虑以下优化:
python复制# 记录子树大小的节点定义
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
self.size = 1 # 包括自身在内的节点总数
def build_size_aware_bst(root):
if not root:
return 0
root.size = 1 + build_size_aware_bst(root.left) + build_size_aware_bst(root.right)
return root.size
当BST需要动态插入元素时(如数据流场景),简单的解法效率会下降。这时可以考虑:
在实际项目中,BST的第k小元素问题有几个典型应用场景:
在实现时需要考虑:
根据不同的应用场景,给出以下选择建议:
对于面试场景,建议先给出递归解法,再优化为迭代实现,最后讨论进阶优化可能。这展示了从基础到优化的完整思考过程。