力扣 460. LFU 缓存

题目来源:https://leetcode.cn/problems/lfu-cache/

大致题意:
设计一个 LFU 缓存类:

  • LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象
  • int get(int key) - 如果键 key 存在于缓存中,则获取键的值,否则返回 - 1
  • void put(int key, int value) - 如果键 key 已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量 capacity 时,则应该在插入新项之前,移除最不经常使用的项。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最近最久未使用 的键

思路

使用 TreeMap 维护一个 key 为使用次数,value 为键值对的有序集合

那么每次缓存放满后,从使用次数最小的键值对中删除最近最久未使用的键值对

具体的:

  1. 使用一个 countMap 维护使用次数与对应键值对的有序集合
  2. 设计一个双向链表,链表节点内存入键值对
  3. 使用一个 keyMap 维护当前已有的键值对与对应的使用次数
  4. 使用一个 nodeMap 维护当前已有的键值对与对应的链表节点

那么对于 get(int key) 方法:

  1. 首先判断 nodeMap 中是否有 key 对应的键值对,若没有则返回 -1;若有,进入下一步
  2. 获取键值对的使用次数,从原使用次数对应的链表中删去该节点(删除后若链表为空,则在 countMap 中删除该使用次数),然后在新使用次数链表中插入该键值对
  3. 更新该键值对的使用次数

对于 put(int key, int value) 方法:

  1. 首先判断 nodeMap 中是否有 key 对应的键值对,若没有则进入下一步;若有,则从 nodeMap 中获取该键值对对应的 node,更新 value,然后更新该键值对的使用次数(从原使用次数链表中删除,插入新使用次数链表,使用次数++)
  2. 判断容量是否为 0,为 0 直接返回;不为 0 判断容量是否已满,若满了删除使用次数最小对应的键值对链表中最近最久未使用的键值对(删除后若链表为空,删除该使用次数),从 keyMap、nodeMap 中移除;若未满进入下一步
  3. 创建 node 节点,存入键值对,将其插入 keyMap、nodeMap 和 使用次数为 1 的链表
class LFUCache {

	TreeMap<Integer, Node> countMap;    // key 为每种使用次数,value 为对应的键的双向链表
    Map<Integer, Integer> map;  // key 缓存中已有的键值对, value 为键值对对应的次数
    Map<Integer, Node> nodeMap; // key 为缓存中键值对的键,value 为该键值对对应的 node
    int capacity;   // 容量
    int size;		// 当前存的键值对个数

    public LFUCache(int capacity) {
        countMap = new TreeMap<>();
        map = new HashMap<>();
        nodeMap = new HashMap<>();
        this.capacity = capacity;
        size = 0;
    }

    /**
     * 获取 key 对应的键值对
     *
     * @param key
     * @return
     */
    public int get(int key) {
        if (nodeMap.containsKey(key)) {
            int count = keyMap.get(key);   // 获取对应键值对使用次数
            Node node = nodeMap.get(key);   // 获取键值对对应的 node
            // 使用次数 +1
            addCount(key, count, node);
            return node.val;
        }
        return -1;
    }

    /**
     * 将键值对放入缓存
     * 
     * @param key
     * @param value
     */
    public void put(int key, int value) {
        if (nodeMap.containsKey(key)) {
            int count = keyMap.get(key);   // 获取对应键值对使用次数
            Node node = nodeMap.get(key);   // 获取键值对对应的 node
            // 更新 node 值
            node.val = value;
            // 使用次数 +1
            addCount(key, count, node);
        } else {
            if (capacity == 0) {
                return;
            }
            Node node = new Node(key, value);
            // 如果容量已经满了,移除最不经常使用的键值对
            if (size == capacity) {
                // 移除使用次数最少的链表中最不经常使用的键值对
                // 获取使用次数最少的链表
                Map.Entry<Integer, Node> firstEntry = countMap.entrySet().iterator().next();
                // 获取链表
                Node head = firstEntry.getValue();
                // 移除最不经常使用的键值对
                Node removeNode = head.pre;
                remove(removeNode);
                keyMap.remove(removeNode.key);
                nodeMap.remove(removeNode.key);
                // 如果链表为空,移除该链表
                if (head.pre == head) {
                    countMap.remove(firstEntry.getKey());
                }
                size--;
            }
            size++;
            // 获取使用次数为 1 的链表
            Node head = countMap.getOrDefault(1, new Node(-1, -1));
            // 加入该键值对
            insert(head, node);
            countMap.putIfAbsent(1, head);
            nodeMap.put(key, node);
            keyMap.put(key, 1);
        }
    }

    /**
     * 在键值对原来使用次数的键值对链表中删除该键值对,并插入新的使用次数对应链表
     * 
     * @param key       键值对的 key
     * @param count     键值对使用次数
     * @param node      键值对所在 node
     */
    public void addCount(int key, int count, Node node) {
        remove(node);   // 在旧链表中移除 node
        // 判断旧链表是否为空
        // 首先获取旧链表头节点
        Node head = countMap.get(count);
        // 判断是否为空
        if (head.next == head) {
            // 删除该链表
            countMap.remove(count);
        }
        head = countMap.getOrDefault(count + 1, new Node(-1, -1)); // 获取使用次数 +1 对应的链表头节点
        insert(head, node);   // 将 node 插入新链表中
        countMap.putIfAbsent(count + 1, head);  // 将新链表放入哈希表
        // 更新使用次数
        keyMap.put(key, count + 1);
    }


    /**
     * 在 head 对应的 LRU 链表中插入 node
     * 
     * @param head  链表头节点
     * @param node  待插入的节点
     */
    public void insert(Node head, Node node) {
        node.next = head.next;
        head.next = node;
        node.next.pre = node;
        node.pre = head;
    }

    /**
     * 从 node 原 LRU 链表中删除 node
     * 
     * @param node
     */
    public void remove(Node node) {
        node.next.pre = node.pre;
        node.pre.next = node.next;
    }

    class Node {
        Node pre;
        Node next;
        int val;
        int key;

        public Node(int key, int val) {
            this.key = key;
            this.val = val;
            this.pre = this;
            this.next = this;
        }
    }
}

/**
 * Your LFUCache object will be instantiated and called as such:
 * LFUCache obj = new LFUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三更鬼

谢谢老板!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值