☰
LeetCode 895:最大频率栈的设计与实现解析
2026/10/27 16:44:35 网站建设 项目流程

1. 最大频率栈问题解析

第一次看到LeetCode 895这道题时,我正喝着咖啡准备开始一天的刷题。题目描述很简单:设计一个类似栈的数据结构,但pop操作需要返回出现频率最高的元素。如果多个元素频率相同,就返回最靠近栈顶的那个。我的第一反应是——这不就是给普通栈加了个"统计员"吗?

1.1 问题核心需求

这个数据结构需要支持两个操作:

  • push(val):将整数val压入栈
  • pop():移除并返回栈中出现频率最高的元素。如果频率相同,返回最近被压入的那个

举个例子:

freqStack = FreqStack() freqStack.push(5) # 栈 [5] freqStack.push(7) # 栈 [5,7] freqStack.push(5) # 栈 [5,7,5] freqStack.push(7) # 栈 [5,7,5,7] freqStack.push(4) # 栈 [5,7,5,7,4] freqStack.push(5) # 栈 [5,7,5,7,4,5] freqStack.pop() # 返回5,因为5出现3次(其他最多2次)→栈[5,7,5,7,4] freqStack.pop() # 返回7,因为5和7都出现2次,但7更靠近栈顶→栈[5,7,5,4] freqStack.pop() # 返回5→栈[5,7,4] freqStack.pop() # 返回4→栈[5,7]

1.2 为什么这是个困难题

这道题被标记为"困难"有几个原因:

  1. 需要同时维护频率信息和入栈顺序
  2. 常规栈结构无法直接满足需求
  3. 需要设计高效的数据结构组合(时间复杂度要求O(1))

我在白板上画了半小时才理清思路——原来需要三个核心组件:

  • 频率计数器(哈希表):记录每个值的出现次数
  • 频率到元素栈的映射:相同频率的元素按入栈顺序保存
  • 当前最大频率值:快速定位要弹出的元素

2. 数据结构设计与实现

2.1 核心数据结构选择

经过多次尝试,我发现最优解需要以下数据结构组合:

  1. unordered_map<int, int> freq:记录每个值的出现频率
  2. unordered_map<int, stack<int>> group:按频率分组保存元素
  3. int maxFreq:记录当前最大频率

关键点:当元素被push时,它的频率会增加,并被放入对应频率的栈中。pop时直接从maxFreq对应的栈顶取元素。

2.2 C++实现详解

class FreqStack { private: unordered_map<int, int> freq; unordered_map<int, stack<int>> group; int maxFreq = 0; public: void push(int val) { int f = ++freq[val]; // 更新频率 maxFreq = max(maxFreq, f); // 更新最大频率 group[f].push(val); // 放入对应频率栈 } int pop() { int val = group[maxFreq].top(); // 获取当前最高频率元素 group[maxFreq].pop(); // 移除该元素 freq[val]--; // 更新频率 // 如果当前频率栈为空,降低maxFreq if (group[maxFreq].empty()) { maxFreq--; } return val; } };

2.3 时间复杂度分析

  • push操作:O(1)
    • 哈希表查找和更新:O(1)
    • 栈操作:O(1)
  • pop操作:O(1)
    • 栈顶访问和弹出:O(1)
    • 哈希表更新:O(1)
    • maxFreq调整:O(1)

3. 关键问题与解决方案

3.1 为什么需要group结构

这是我最初设计时踩过的坑。如果只用频率计数器,当多个元素频率相同时,无法快速确定哪个更靠近栈顶。group结构完美解决了这个问题——每个频率对应一个栈,自然保持了入栈顺序。

3.2 边界条件处理

几个容易出错的场景:

  1. 连续pop导致maxFreq递减的情况
  2. 相同元素多次push后的频率更新
  3. 当所有元素频率都为1时的行为(退化为普通栈)

我的测试用例:

# 测试用例1:相同元素多次push stack.push(1) stack.push(1) stack.push(2) assert stack.pop() == 1 # 频率2 > 1 assert stack.pop() == 2 # 频率都为1,2更靠近栈顶 # 测试用例2:maxFreq递减 stack.push(3) stack.push(3) stack.push(2) stack.push(2) stack.pop() # 3 (freq=2) stack.pop() # 2 (freq=2) stack.pop() # 3 (freq=1)

4. 不同语言实现对比

4.1 Java版本特点

class FreqStack { private Map<Integer, Integer> freq; private Map<Integer, Deque<Integer>> group; private int maxFreq; public FreqStack() { freq = new HashMap<>(); group = new HashMap<>(); maxFreq = 0; } public void push(int val) { int f = freq.getOrDefault(val, 0) + 1; freq.put(val, f); maxFreq = Math.max(maxFreq, f); group.computeIfAbsent(f, z->new ArrayDeque()).push(val); } public int pop() { int val = group.get(maxFreq).pop(); freq.put(val, freq.get(val)-1); if (group.get(maxFreq).isEmpty()) { maxFreq--; } return val; } }

Java注意点:

  1. 使用Deque替代Stack(性能更好)
  2. computeIfAbsent简化group初始化
  3. 需要处理null的情况(getOrDefault)

4.2 Python实现技巧

from collections import defaultdict class FreqStack: def __init__(self): self.freq = defaultdict(int) self.group = defaultdict(list) self.max_freq = 0 def push(self, val: int) -> None: self.freq[val] += 1 self.max_freq = max(self.max_freq, self.freq[val]) self.group[self.freq[val]].append(val) # 用list模拟栈 def pop(self) -> int: val = self.group[self.max_freq].pop() self.freq[val] -= 1 if not self.group[self.max_freq]: self.max_freq -= 1 return val

Python特色:

  1. 使用defaultdict避免键检查
  2. 用list的append/pop模拟栈(Python中栈通常用list实现)
  3. 不需要显式初始化内部数据结构

5. 实际应用场景

这种数据结构在以下场景很有价值:

  1. 缓存系统:优先淘汰高频访问项
  2. 推荐系统:展示高频交互内容
  3. 负载均衡:路由高频请求到不同服务器
  4. 数据分析:实时统计流数据中的高频元素

我在工作中曾用类似结构实现过一个实时热点新闻系统——每当用户点击新闻时调用push,展示热点时调用pop,效果非常好。

6. 常见面试问题

根据我的面试经验,面试官常会追问:

  1. 如果要求pop返回频率最低的元素怎么修改?

    • 解决方案:维护minFreq并同步更新
  2. 如果数据量非常大(内存放不下)怎么办?

    • 解决方案:使用外部存储+缓存,或近似算法
  3. 如何扩展支持多线程?

    • 解决方案:对共享变量加锁(但会影响性能)
  4. 如果pop需要返回最早入栈的元素怎么处理?

    • 解决方案:额外维护一个时间戳队列

7. 优化与变种

7.1 空间优化方案

当元素频率降低时,group中的低频率栈可能永远不会再被访问。可以添加清理逻辑:

void cleanup(int freq) { if (group[freq].empty()) { group.erase(freq); } }

7.2 支持频次阈值

实际应用中可能需要只关注高频元素:

void push(int val, int threshold) { if (++freq[val] >= threshold) { group[freq[val]].push(val); maxFreq = max(maxFreq, freq[val]); } }

7.3 多维度频率统计

扩展支持时间维度频率:

struct TimestampedVal { int val; time_t timestamp; }; unordered_map<int, stack<TimestampedVal>> timeGroup;

8. 刷题建议

对于这类设计题,我的刷题方法是:

  1. 先理解常规数据结构(栈/哈希表)的特性
  2. 在白板上画出操作流程
  3. 编写测试用例验证边界条件
  4. 思考时间/空间复杂度
  5. 尝试不同语言的实现

这道题在LeetCode周赛中出现过多次变种,建议把相关题目都练习一遍:

    1. LFU Cache
    1. All O`one Data Structure
    1. Insert Delete GetRandom O(1)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询