在问 · 算法题解

895. 最大频率栈

困难设计哈希表 + 按频率分组的栈力扣原题 ↗

题目描述

设计一个类似栈的类 FreqStack,每次弹出的是当前出现次数最多的元素。

FreqStack():创建一个空栈。

push(val):把整数 val 放到栈顶,不返回值。

pop():在栈中当前所有元素里,找出出现次数最多的值,移除并返回它。若有多个值出现次数并列最多,则在这些值中选取离栈顶最近的那一个元素移除并返回。

示例 1
输入:操作 = ["FreqStack","push","push","push","push","push","push","pop","pop","pop","pop"]
参数 = [[],[5],[7],[5],[7],[4],[5],[],[],[],[]]
输出:[null,null,null,null,null,null,null,5,7,5,4]

解释:依次弹出:5 出现 3 次最多;5 和 7 都 2 次,7 更靠顶;5 剩 2 次最多;三者各 1 次,4 最靠顶。

数据范围

  • 0 <= val <= 10^9
  • push 和 pop 的总调用次数不超过 2 * 10^4。
  • 保证调用 pop 时栈非空。

解题思路:哈希表 + 按频率分组的栈

用 freq 记录每个值当前的出现次数,用 group[f] 保存“第 f 次出现”的那些压入动作组成的小栈,再记录最高频率 maxf。push 时把值的频率加 1 得到 f,压入 group[f];pop 时从 group[maxf] 弹出栈顶,它就是频率最高中最近的那个,把它的频率减 1,如果 group[maxf] 空了就 maxf -= 1。一个出现 3 次的值同时在 group[1]、group[2]、group[3] 里,所以频率降低后不需要搬动它。

为什么正确

group[1..maxf] 都非空,且值 v 恰好出现在 group[1..freq[v]] 中各一次;每个 group 内部按压入时间排列。

复杂度分析

push 和 pop 均为 O(1);空间 O(n)。

易错点

很多人想用堆按 (频率, 时间) 排序,但弹出后另一份同值的频率变了,堆里的旧记录会失效。按频率分栈的关键是:不要把值从低频栈移到高频栈,而是每一层都保留一份。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class FreqStack:
    def __init__(self):
        self.freq = {}
        self.group = {}
        self.maxf = 0

    def push(self, val: int) -> None:
        f = self.freq.get(val, 0) + 1
        self.freq[val] = f
        if f > self.maxf:
            self.maxf = f
        if f not in self.group:
            self.group[f] = []
        self.group[f].append(val)

    def pop(self) -> int:
        val = self.group[self.maxf].pop()
        self.freq[val] -= 1
        if not self.group[self.maxf]:
            self.maxf -= 1
        return val

载入编辑器,自己跑一遍 →

其他解法

优先队列把排序规则交给堆来维护,是通用做法;分组栈利用频率只会加减 1 且最大频率连续变化的特点,把两个操作都做到 O(1)。

解法 2:优先队列(按频率、时间戳排序) 时间 push / pop 均 O(log n),空间 O(n)

用哈希表记录每个值当前的频率,再维护一个存放三元组 (频率, 时间戳, 值) 的大根堆。push 时频率加 1,把 (新频率, 递增的时间戳, val) 压入堆;pop 时取出堆顶:频率最大的优先,频率相同时时间戳大(离栈顶最近)的优先,再把该值的频率减 1。同一个值的每次入栈都对应堆里的一个独立元素,所以堆中不会有过期数据。

把“先比频率、再比先后”的规则直接写成比较器,很容易想到也容易写对。代价是每次操作多一个 log n,而且没有利用“频率每次只变化 1”这一特点。

class FreqStack:
    def __init__(self):
        self.freq = {}
        self.heap = []
        self.time = 0

    def push(self, val: int) -> None:
        f = self.freq.get(val, 0) + 1
        self.freq[val] = f
        self.time += 1
        heapq.heappush(self.heap, (-f, -self.time, val))

    def pop(self) -> int:
        val = heapq.heappop(self.heap)[2]
        self.freq[val] -= 1
        return val

设计 专题的其他题目

查看 设计 全部题目 →