leetcode 460. LFU 缓存

题目描述
请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。
实现 LFUCache 类:
LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象

  • int get(int key) - 如果键存在于缓存中,则获取键的值,否则返回 -1。
  • void put(int key, int value) - 如果键已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量时,则应该在插入新项之前,使最不经常使用的项无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最近最久未使用 的键。
    注意「项的使用次数」就是自插入该项以来对其调用 get 和 put 函数的次数之和。使用次数会在对应项被移除后置为 0 。

为了确定最不常使用的键,可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。

当一个键首次插入到缓存中时,它的使用计数器被设置为 1 (由于 put 操作)。对缓存中的键执行 get 或 put 操作,使用计数器的值将会递增。

示例:
输入:
[“LFUCache”, “put”, “put”, “get”, “put”, “get”, “get”, “put”, “get”, “get”, “get”]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [3], [4, 4], [1], [3], [4]]
输出:
[null, null, null, 1, null, -1, 3, null, -1, 3, 4]
解释:
// cnt(x) = 键 x 的使用计数
// cache=[] 将显示最后一次使用的顺序(最左边的元素是最近的)
LFUCache lFUCache = new LFUCache(2);
lFUCache.put(1, 1); // cache=[1,_], cnt(1)=1
lFUCache.put(2, 2); // cache=[2,1], cnt(2)=1, cnt(1)=1
lFUCache.get(1); // 返回 1
// cache=[1,2], cnt(2)=1, cnt(1)=2
lFUCache.put(3, 3); // 去除键 2 ,因为 cnt(2)=1 ,使用计数最小
// cache=[3,1], cnt(3)=1, cnt(1)=2
lFUCache.get(2); // 返回 -1(未找到)
lFUCache.get(3); // 返回 3
// cache=[3,1], cnt(3)=2, cnt(1)=2
lFUCache.put(4, 4); // 去除键 1 ,1 和 3 的 cnt 相同,但 1 最久未使用
// cache=[4,3], cnt(4)=1, cnt(3)=2
lFUCache.get(1); // 返回 -1(未找到)
lFUCache.get(3); // 返回 3
// cache=[3,4], cnt(4)=1, cnt(3)=3
lFUCache.get(4); // 返回 4
// cache=[3,4], cnt(4)=2, cnt(3)=3

清华操作系统视频
leetcode 460. LFU 缓存
leetcode 460. LFU 缓存
比较复杂,使用的是双链表的数据结构,但是每个双链表里面的节点又是由双链表组成的,里层的双链表结构类似于LRU的双链表,外层的双链表是通过节点的个数区分的,也就是说内层的双链表节点的个数是一致的。

  • 外层节点

    • 访问操作,需要将内层节点删除,加入到下一个外层节点中,如果外层节点不存在需要创建一个新的外层节点,节点个数是访问的内层节点个数加1. 如果外层节点存在就需要删除该内层节点,将该内层节点加入到下一个外层节点中(在外层节点中删除内层节点时,不要直接delete,因为该节点会插入到下层)。
    • 插入操作,需要插入到cnt=1 的外层节点中,且在内层节点的位置是头结点的位置。如果插入的时候缓存区满了,那么需要删除外层节点最开始的位置里面的最右边的节点
  • 内层节点(类似于LRU算法)

  • 如果外层节点为空需要将该外层节点全部删除,不然遍历的时候全是空节点

class LFUCache {
public:
    struct Node {
        Node *left, *right;
        int key, val;
        Node(int _key, int _val) {
            key = _key, val = _val;
            left = right = NULL;
        }
    };//创建内层节点的双链表
    struct Block {
        Block *left, *right;
        Node *head, *tail;
        int cnt;
        Block(int _cnt) {
            cnt = _cnt;
            left = right = NULL;
            head = new Node(-1, -1), tail = new Node(-1, -1);
            head->right = tail, tail->left = head;
        }//初始化块
        ~Block() {
            delete head;
            delete tail;
        }//当对象的生命周期结束时,撤销类对象时候会自动调用析构函数。
        void insert(Node* p) {
            p->right = head->right;
            head->right->left = p;
            p->left = head;
            head->right = p;
        }
        void remove(Node* p) {
            p->left->right = p->right;
            p->right->left = p->left;
        }
        bool empty() {
            return head->right == tail;
        }
    }*head, *tail;
    int n;
    unordered_map<int, Block*> hash_block;
    unordered_map<int, Node*> hash_node;

    void insert(Block* p) {  // 在p的右侧插入新块,cnt是p->cnt + 1
        auto cur = new Block(p->cnt + 1);
        cur->right = p->right;
        p->right->left = cur;
        p->right = cur;
        cur->left = p;
    }

    void remove(Block* p) {
        p->left->right = p->right;
        p->right->left = p->left;
        delete p;
    }

    LFUCache(int capacity) {
        n = capacity;
        head = new Block(0), tail = new Block(INT_MAX);
        head->right = tail, tail->left = head;//一开始外层节点没有任何内容
    }

    int get(int key) {
        if (hash_block.count(key) == 0) return -1;
        auto block = hash_block[key];
        auto node = hash_node[key];
        block->remove(node);
        if (block->right->cnt != block->cnt + 1) insert(block);
        block->right->insert(node);
        hash_block[key] = block->right;
        if (block->empty()) remove(block);//如果当前块删除了内层节点后变空,那么该块也需要被删除。
        return node->val;
    }

    void put(int key, int value) {
        if (!n) return;//如果缓存空间为0,那么直接返回即可
        if (hash_block.count(key)) {
            hash_node[key]->val = value;
            get(key);
        } //如果块中有该值,那么直接使用get方法即可,意思是块加一操作
        else 
        {
            if (hash_block.size() == n) 
            {
                auto p = head->right->tail->left;
                head->right->remove(p);
                if (head->right->empty()) remove(head->right);
                hash_block.erase(p->key);
                hash_node.erase(p->key);
                delete p;
            }//内存满的情况,删除块里最右边的节点
            
            //反之,创建节点,如果没有cnt==1的块,创建块。
            //在cnt==1的块中起始位置插入节点
            //修改块hash表和内层节点hash表
            auto p = new Node(key, value);
            if (head->right->cnt > 1) insert(head);
            head->right->insert(p);
            hash_block[key] = head->right;
            hash_node[key] = p;
            
        }
    }
};

/**
 * 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);
 */

链接:https://www.acwing.com/activity/content/code/content/555766/

上一篇:460. LFU Cache 访问频率最低的元素


下一篇:LeetCode 460. LFU缓存(哈希双链表)