Leetcode 460 LFU缓存机制(最低频次最少使用)

// LFU比LRU更复杂一些
// 具体来说,在get操作时,如果key存在, 则需要频次+1并插入链表头
// 如果key不存在,则直接返回-1
// put操作时, 当key已经存在,与get操作类似,只是把value值进行更新
// 当key不存在时,判断容量大小,是否需要删除key
// 删除的时候,得删除频次最低的那个key
// 当存在多个频次最低的key时, 删除最久未使用的那个
// 整体思路就是一个hashmap存key和key-value的映射,保证取key是O(1)操作
// 还要一个hashmap存频次与key-value的映射,保证在频次操作添加和删除节点是O(1)
// 注意,因为同一个频次可以对应多个key,所以此hashmap的后一项是一个双向链表
// 插入频次在双向链表头部,删除频次在双向链表尾部
// 单独封装一个双向链表, 插入,删除, 删除末尾元素,判空更便于操作
// 同时,还需要一个当前LFU的最低频次,这在插入和删除键的时候得用到
// 例如,刚开始插入一个key,最低频次是1,但是多次get或者put之后,最低频次是会改变的
// 即当cache的容量为1时,get已存在的key时,假设此时key频次为1,需要删除频次为1的hashmap中的对应双向链表
// 还得创建频次为2的hashmap与双向链表映射, 将key插入其中。

class LFUCache {

    int minFreq;
    int capacity;
    HashMap<Integer, ListNode> keyTable;
    HashMap<Integer, DoublyLinkedList> freqTable;


    public LFUCache(int capacity) {
        this.capacity = capacity;
        minFreq = 0;
        keyTable = new HashMap<>();
        freqTable = new HashMap<>();
    }

    public int get(int key) {

        if (!keyTable.containsKey(key)) // 键不存在直接返回
            return -1;

        ListNode node = keyTable.get(key); // 获取key-value节点
        int tmp = node.freq; // 获取key当前频次
        node.freq++; // 更新key的频次

        //在key所在频次链表中的key

        DoublyLinkedList dLink = freqTable.get(tmp); // 获取key同频次的双向链表
        dLink.removeNode(node);  //将顶点从此频次链表移除
        if (dLink.isEmpty()) { // 此频次链表原来只有key一个值,现在为空了
            freqTable.remove(tmp); // 删除该频次的映射和双向链表
            if (minFreq == tmp) // 如果此时删除的该频次最后一个key就是整个LFU的最低频次
                minFreq++;     // 表示此时cache中存在的最低频次要随着key进行+1操作
        }

        // 将key插入到频次+1的对应链表中

        tmp++;
        if (!freqTable.containsKey(tmp)) { // 注意如果此频次不存在,需要先new一个链表
            freqTable.put(tmp, new DoublyLinkedList());
        }
        freqTable.get(tmp).addNode(node);
        return node.val;
    }

    public void put(int key, int value) {

        if (capacity == 0) // 数据中存在容量为0的情况,导致错误
            return ;

        // key不在cache中
        if (!keyTable.containsKey(key)) {
            ListNode node = new ListNode(key, value, 1); // 创建新的节点存放key-value
            if (keyTable.size() < capacity) { // 未超过cache容量
//                if (!freqTable.containsKey(1)) // 不存在频次1的频次与链表映射时 这一步可以省略
//                    freqTable.put(1, new DoublyLinkedList());
                ;
            }else { // 需要删除频次最低的key
                DoublyLinkedList dLink = freqTable.get(minFreq); // 获取频次最低的双向链表
                int delKey = dLink.removeTail(); // 从双向链表中删除尾节点
                if (dLink.isEmpty()){ // 若删除key导致链表为空
                    freqTable.remove(minFreq); // 则删除该频次与链表银蛇
                    minFreq++; // 最低频次必然是更高一次的频次
                }
                keyTable.remove(delKey); // 在key映射中删除key
            }
            keyTable.put(key, node); // 添加key映射
            if (!freqTable.containsKey(1)) // 注意:可能之前的get操作导致频次为1的映射已经移除了
                freqTable.put(1, new DoublyLinkedList());
            freqTable.get(1).addNode(node);
            minFreq = 1;

        }else {

            // key在cache中 与get大致相同,只是多了一个更新value的操作

            ListNode node = keyTable.get(key);
            node.val = value;
            int tmp = node.freq;
            node.freq++;

            DoublyLinkedList dLink = freqTable.get(tmp);
            dLink.removeNode(node);
            if (dLink.isEmpty()) {
                freqTable.remove(tmp);
                if (minFreq == tmp)
                    minFreq++;
            }
            tmp++;
            if (!freqTable.containsKey(tmp)) {
                freqTable.put(tmp, new DoublyLinkedList());
            }
            freqTable.get(tmp).addNode(node);
        }
    }
}

class ListNode {
    int key;
    int val;
    int freq;
    ListNode pre;
    ListNode next;

    public ListNode() {}
    public ListNode(int key, int val, int freq) {
        this.key = key;
        this.val = val;
        this.freq = freq;
        pre = null;
        next = null;
    }
}

class DoublyLinkedList {
    ListNode head;
    ListNode tail;

    public DoublyLinkedList() {
        head = new ListNode(); // 两个哨兵方便链表操作
        tail = new ListNode();
        head.next = tail;
        tail.pre = head;
    }

    public void addNode(ListNode node) { // 添加到头部
        node.next = head.next;
        node.pre = head;
        head.next.pre = node;
        head.next = node;
    }

    public void removeNode(ListNode node) { //删除节点
        node.next.pre = node.pre;
        node.pre.next = node.next;
    }

    public int removeTail() { // 移除尾部节点
        ListNode node = tail.pre;
        removeNode(node);
        return node.key;
    }

    public boolean isEmpty() { // 判断链表是否为空
        if (head.next == tail)
            return true;
        return false;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值