最大频率栈

249 阅读4分钟

「这是我参与2022首次更文挑战的第19天,活动详情查看:2022首次更文挑战

前言

笔者除了大学时期选修过《算法设计与分析》和《数据结构》还是浑浑噩噩度过的(当时觉得和编程没多大关系),其他时间对算法接触也比较少,但是随着开发时间变长对一些底层代码/处理机制有所接触越发觉得算法的重要性,所以决定开始系统的学习(主要是刷力扣上的题目)和整理,也希望还没开始学习的人尽早开始。

系列文章收录《算法》专栏中。

力扣题目链接

问题描述

设计一个类似堆栈的数据结构,将元素推入堆栈,并从堆栈中弹出出现频率最高的元素。

实现 FreqStack 类:

  • FreqStack() 构造一个空的堆栈。
  • void push(int val) 将一个整数 val 压入栈顶。
  • int 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]
解释:
FreqStack = new 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 出现频率最高。堆栈变成 [5,7,5,7,4]。
freqStack.pop ();//返回 7 ,因为 5 和 7 出现频率最高,但7最接近顶部。堆栈变成 [5,7,5,4]。
freqStack.pop ();//返回 5 ,因为 5 出现频率最高。堆栈变成 [5,7,4]。
freqStack.pop ();//返回 4 ,因为 4, 5 和 7 出现频率最高,但 4 是最接近顶部的。堆栈变成 [5,7]。

提示:

  • 0 <= val <= 109
  • push 和 pop 的操作数不大于 2 * 104。
  • 输入保证在调用 pop 之前堆栈中至少有一个元素。

确定学习目标

对《最大频率栈》的算法过程进行剖析。

剖析

  1. 题目其实有点误导,看完题给人的感觉是要实现入栈出栈,但是出栈的时候需要返回频率最高的元素,当频率最高的元素不止一个的时候还要返回最近栈顶的元素。下面我们进行分析:

入栈操作看不出来啥。出栈操作需要返回频率最高的元素,明显不是普通的栈结构能实现的,但是当相同的元素时需要返回最近栈顶的(也就是最近放入的),所以我们可以发现满足题目的需求主要保证放入重复的元素按次数划分,内部使用栈结构就行了。

所以我们可以使用一个Map<Integer, Stack<Object>> key为元素重复次数,value为栈用于存放元素的map结构存放,满足当次数相同时返回最近放入的。

  1. 但是这个map的数据是怎么填充的呢?
  • 我咋知道,存放进Map<Integer, Stack<Object>>的元素是重复了几次,拿到我每次放的时候再去遍历一遍统计次数?当然可以不用,我们直接使用一个Map<Integer, Object>key为元素,value为重复次数的map结构存放即可,每次put的时候累加。

  • Map<Integer, Object>的value累加的时候需要更新,怎么避免直接put性能比较低呢?自己定义一个Element,map中的value存放的是一个引用,我们直接拿到引用进行set就行。为啥不直接用Integer,Integer不就是一个引用吗?是的,但是对Integer重新赋值只能用字面变量赋值,这样其实引用地址都已经变了所以达不到该效果。

  1. 那么怎么每次pop的时候弹出最大频率的元素呢?
  • 直接使用1个标记变量标记最大频率出现的次数。
  • 那最大频率弹出了我咋拿到下一个最大频率的次数呢?次数是累加的,弹出标记变量减1就行。

代码

注意:如果直接使用下面代码提交力扣记得把main函数去掉会消耗时间。

另外:可以发现我在pop函数中注释了两块对两个map进行remove,本意是如果真的用在生产环境可以及时释放空间,但是remove会进行检索会消耗时长又考虑到如果真的用在生产中肯定不是这么玩的map会因为put一直消耗内存,所以还是注释掉了。

package com.study.algorithm.stack;

import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

/**
 * 895. 最大频率栈
 */
public class FreqStack {
    class Element {
        private int ele;

        public int getEle() {
            return ele;
        }

        public void setEle(int ele) {
            this.ele = ele;
        }

        public Element(int ele) {
            this.ele = ele;
        }
    }

    /**
     * key存放元素,value存放元素出现的次数
     */
    private Map<Integer, Element> elementAndCountMap = new HashMap<>();
    /**
     * key存放出现的次数,value存放次数对应的元素们
     */
    private Map<Integer, Stack<Element>> elementCountAndElementsMap = new HashMap<>();

    /**
     * 最大次数
     */
    private Integer maxCount = 0;

    public FreqStack() {

    }

    public void push(int val) {
        Element element = elementAndCountMap.get(val);
        if (element != null) {
            int ele = element.getEle();
            int elementCount = ele + 1;
            element.setEle(elementCount);

            //更新最大值
            if (elementCount > maxCount) {
                maxCount = elementCount;
            }

            updateOrInsertElementStack(elementCount, new Element(val));
        } else {
            if (maxCount == 0) {
                maxCount = 1;
            }
            int elementCount = 1;
            Element newElement = new Element(elementCount);
            elementAndCountMap.put(val, newElement);

            updateOrInsertElementStack(elementCount, new Element(val));
        }

    }

    private void updateOrInsertElementStack(int elementCount, Element element) {
        Stack<Element> elements = elementCountAndElementsMap.get(elementCount);
        if (elements != null) {
            elements.push(element);
        } else {
            Stack<Element> elementStack = new Stack<>();
            elementStack.push(element);
            elementCountAndElementsMap.put(elementCount, elementStack);
        }
    }

    public int pop() {
        int result = -1;
        Stack<Element> elementStack = elementCountAndElementsMap.get(maxCount);
        if (elementStack == null) {
            return result;
        }

        Element pop = elementStack.pop();
        result = pop.getEle();

        Element element = elementAndCountMap.get(result);
        int ele = element.getEle();
        ele--;
        element.setEle(ele);
//        if (ele == 0) {
//            elementAndCountMap.remove(result);
//        }

        int size = elementStack.size();
        if (size == 0) {
//            elementCountAndElementsMap.remove(maxCount);
            maxCount--;
        }

        return result;
    }

    public static void main(String[] args) {
        FreqStack freqStack = new FreqStack();
        freqStack.push(56);
        freqStack.push(17);
        freqStack.push(35);
        freqStack.push(96);
        freqStack.push(9);
        freqStack.pop();
        freqStack.push(98);
        freqStack.pop();
        freqStack.push(41);
        freqStack.pop();
        freqStack.push(50);
        freqStack.pop();
        freqStack.push(14);
        freqStack.pop();
        freqStack.pop();
        freqStack.pop();
        freqStack.pop();
        freqStack.pop();
        freqStack.pop();
    }
}