ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

LeetCode 501题解析:BST中序遍历与众数统计

LeetCode 501题解析:BST中序遍历与众数统计

1. LeetCode 501 题核心知识点解析

作为刷过300+题的过来人,我发现在二叉搜索树(BST)类题目中,501题《二叉搜索树中的众数》是个非常典型的考察点。这道题不仅要求掌握BST的中序遍历特性,还需要灵活运用哈希表和指针操作来统计频率。下面我会从解题思路、代码实现到优化技巧,完整拆解这道高频面试题。

关键提示:BST的中序遍历结果必然是有序数组,这是解决本题的核心突破口

1.1 题目重述与示例分析

给定一个有重复值的二叉搜索树(BST),要求找出其中出现频率最高的元素(众数)。注意:

  • 众数可能有多个(出现次数相同)
  • 进阶要求:不使用额外空间(递归栈除外)

示例:

输入: [1,null,2,2] 1 \ 2 / 2 输出: [2]

这个例子中,2出现了两次,而1只出现一次,因此众数是2。如果是[1,1,2,2]则应该返回[1,2]。

1.2 暴力解法与哈希表统计

最直观的思路是遍历整棵树,用哈希表统计每个值出现的次数:

from collections import defaultdict class Solution: def findMode(self, root): count = defaultdict(int) def traverse(node): if not node: return count[node.val] += 1 traverse(node.left) traverse(node.right) traverse(root) max_count = max(count.values()) if count else 0 return [k for k,v in count.items() if v == max_count]

这种方法时间复杂度O(N),空间复杂度O(N),虽然能AC但不满足进阶要求。它的优势在于:

  • 代码直观易理解
  • 适用于任何二叉树(不依赖BST特性)
  • 可以处理val为负数的情况

但面试官通常会追问:"能否利用BST的特性进行优化?"

2. 中序遍历的妙用

2.1 BST的有序特性

BST的中序遍历结果是一个非递减序列。对于示例树:

中序遍历结果:[1,2,2]

我们可以像处理有序数组一样,在遍历时比较当前节点与前驱节点的值:

class Solution: def findMode(self, root): self.prev = None self.current_count = 0 self.max_count = 0 self.result = [] def inorder(node): if not node: return inorder(node.left) # 左 # 处理当前节点 if node.val == self.prev: self.current_count += 1 else: self.current_count = 1 if self.current_count > self.max_count: self.max_count = self.current_count self.result = [node.val] elif self.current_count == self.max_count: self.result.append(node.val) self.prev = node.val inorder(node.right) # 右 inorder(root) return self.result

2.2 双指针法的实现细节

这个解法使用类成员变量保存状态:

  1. prev:记录前驱节点值
  2. current_count:当前值的连续出现次数
  3. max_count:全局最大出现次数
  4. result:结果列表

关键操作流程:

  • 当node.val == prev时,current_count递增
  • 当值变化时,重置current_count = 1
  • 每次更新current_count后与max_count比较:
    • 若更大:清空结果列表,加入当前值
    • 若相等:追加当前值
    • 若更小:不做处理

时间复杂度O(N),空间复杂度O(1)(不考虑递归栈)

3. 迭代解法与Morris遍历

3.1 用栈模拟中序遍历

递归解法可能引发栈溢出,改用迭代:

class Solution: def findMode(self, root): stack = [] curr = root prev = None max_count = current_count = 0 result = [] while stack or curr: while curr: stack.append(curr) curr = curr.left curr = stack.pop() if prev == curr.val: current_count += 1 else: current_count = 1 if current_count > max_count: max_count = current_count result = [curr.val] elif current_count == max_count: result.append(curr.val) prev = curr.val curr = curr.right return result

3.2 Morris遍历实现O(1)空间

终极优化是使用Morris遍历,避免使用递归栈和显式栈:

class Solution: def findMode(self, root): curr = root prev = None max_count = current_count = 0 result = [] while curr: if not curr.left: self.process_node(curr, prev, result, current_count, max_count) prev = curr curr = curr.right else: predecessor = curr.left while predecessor.right and predecessor.right != curr: predecessor = predecessor.right if not predecessor.right: predecessor.right = curr curr = curr.left else: predecessor.right = None self.process_node(curr, prev, result, current_count, max_count) prev = curr curr = curr.right return result def process_node(self, node, prev, result, current_count, max_count): if node.val == prev: current_count += 1 else: current_count = 1 if current_count > max_count: max_count = current_count result.clear() result.append(node.val) elif current_count == max_count: result.append(node.val)

Morris遍历通过修改树结构(临时添加右指针)实现遍历,完成后恢复原状。虽然代码复杂,但在空间限制严格时非常有用。

4. 边界条件与测试案例

4.1 必须考虑的边界情况

  1. 空树:root = None→ 返回[]
  2. 所有值唯一:[1,2,3]→ 返回[1,2,3]
  3. 多个众数:[1,1,2,2]→ 返回[1,2]
  4. 单节点树:[1]→ 返回[1]
  5. 负数值:[-1,-1,-2]→ 返回[-1]

4.2 测试用例设计技巧

建议按以下顺序验证:

def test_findMode(): s = Solution() # 普通情况 root1 = TreeNode(1) root1.right = TreeNode(2) root1.right.left = TreeNode(2) assert s.findMode(root1) == [2] # 多个众数 root2 = TreeNode(1) root2.left = TreeNode(1) root2.right = TreeNode(2) root2.right.left = TreeNode(2) assert sorted(s.findMode(root2)) == [1,2] # 空树 assert s.findMode(None) == [] # 单节点 assert s.findMode(TreeNode(0)) == [0]

5. 复杂度分析与对比

方法时间复杂度空间复杂度适用场景
哈希表统计O(N)O(N)任意二叉树,代码简单
递归中序O(N)O(H)递归栈BST标准解法
迭代中序O(N)O(H)显式栈避免递归栈溢出
Morris遍历O(N)O(1)严格空间限制

实际面试中建议:

  1. 先给出哈希表解法(体现基础能力)
  2. 主动优化到中序遍历解法(展示BST特性理解)
  3. 如果面试官追问,再讨论Morris遍历

6. 常见错误与调试技巧

6.1 高频错误类型

  1. 未重置current_count:当遇到新值时忘记重置计数器

    # 错误示例 if node.val == prev: current_count += 1 # 缺少else分支
  2. 结果更新逻辑错误:

    # 错误示例:没有清空结果列表 if current_count >= max_count: # 应该用> result.append(node.val)
  3. prev初始化问题:使用float('-inf')可能不兼容所有测试用例

6.2 Debug实操建议

  1. 打印中序遍历序列:

    def inorder(node): if not node: return inorder(node.left) print(node.val, end=' ') inorder(node.right)
  2. 添加调试输出:

    print(f"val={node.val}, prev={prev}, count={current_count}, max={max_count}")
  3. 可视化小树结构:

    用缩进表示层级: root \ 2 / 2

7. 举一反三与相似题目

掌握501题后,可以解决以下变种:

  1. BST中前驱后继:利用中序遍历特性

    • 285题《BST中的中序后继》
    • 510题《BST中的中序前驱II》
  2. 频率统计问题:

    • 347题《前K个高频元素》
    • 451题《根据字符出现频率排序》
  3. BST验证与操作:

    • 98题《验证BST》
    • 538题《把BST转换为累加树》

核心思维:BST的中序遍历结果是有序数组,许多问题可以转化为数组问题处理

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进