zoukankan      html  css  js  c++  java
  • 【LeetCode-模拟】LFU缓存

    题目描述

    请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。它应该支持以下操作:get 和 put。

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

    「项的使用次数」就是自插入该项以来对其调用 get 和 put 函数的次数之和。使用次数会在对应项被移除后置为 0 。

    进阶:
    你是否可以在 O(1) 时间复杂度内执行两项操作?

    示例:

    LFUCache cache = new LFUCache( 2 /* capacity (缓存容量) */ );
    
    cache.put(1, 1);
    cache.put(2, 2);
    cache.get(1);       // 返回 1
    cache.put(3, 3);    // 去除 key 2
    cache.get(2);       // 返回 -1 (未找到key 2)
    cache.get(3);       // 返回 3
    cache.put(4, 4);    // 去除 key 1
    cache.get(1);       // 返回 -1 (未找到 key 1)
    cache.get(3);       // 返回 3
    cache.get(4);       // 返回 4
    

    题目链接: https://leetcode-cn.com/problems/lfu-cache/

    思路

    这题和LRU缓存机制很像。
    LRU

    • 缓存满时,删除最久未使用的元素;

    LFU:

    • 缓存满时,删除使用频次最少的元素;

    所以,在 LFU 中,我们还需要记录每个元素被访问的次数,每个元素除了 key,val 之外,还需要定义 freq 表示访问的次数:

    struct Node{
        int key;
        int val;
        int freq;
    
        Node(int key, int val, int freq):key(key), val(val), freq(freq){}
    };
    

    我们使用两个哈希表:unordered_map<int, list<Node>::iterator> keyTable 用来存储 key 到链表节点指针的映射;unordered_map<int, list<Node>> freqTable 用来存储频数 freq 到链表的映射,具体如下图:

    图来自这篇题解,左边的哈希表为 keyTable,右边的哈希表为 freqTale。可以看到 freqTable 根据频数存储了具体的链表,而 keyTable 存储了 key 到链表节点地址的映射。

    除此之外,我们还需要一个变量 minFreq 来存储目前最少访问的次数,通过 freqTable[minFreq].pop_back() 删除使用最少的节点。

    算法步骤:

    • get(key):

      • 如果 key 不在 keyTable 中,返回 -1;
      • 否则,通过 keyTable[key] 获取 key 对应的节点地址 it,然后通过 it 得到节点的 key、val、freq,将节点从 freqTable[freq] 对应的链表删除,然后将该节点加入到 freqTable[freq+1] 对应的链表头;
    • put(key, value):

      • 如果 key 在 keyTable 中,则使用和 get(key) 中的第二步类似的方法;
      • 否则,如果缓存已经满了,则根据 minFreq 删除使用最少的节点,然后设置 minFreq 为 1,将新的节点放入 freqTable[1] 对应的链表头。

    具体代码如下:

    struct Node{
        int key;
        int val;
        int freq;
    
        Node(int key, int val, int freq):key(key), val(val), freq(freq){}
    };
    
    class LFUCache {
    private:
        unordered_map<int, list<Node>::iterator> keyTable;
        unordered_map<int, list<Node>> freqTable;
        int capacity;
        int minFreq;
    public:
        LFUCache(int capacity) {
            this->capacity = capacity;
            this->minFreq = 0;
        }
        
        int get(int key) {
            if(keyTable.count(key)==0) return -1;
            else{
                auto it = keyTable[key];   // it 为 key 对应的节点地址
                int val = it->val;
                int freq = it->freq;
                freqTable[freq].erase(it);  // 在 freqTable[freq] 对应的链表中删除节点
                if(freqTable[freq].size()==0){  // 如果删除后 freqTable[freq] 为空
                    freqTable.erase(freq);
                    if(minFreq==freq) minFreq++; // 注意这一步
                }
                freqTable[freq+1].push_front(Node(key, val, freq+1)); // 将节点放入 freqTable[freq+1] 对应的链表中
                keyTable[key] = freqTable[freq+1].begin();
                return val;
            }
        }
        
        void put(int key, int value) {
            if(this->capacity==0) return;   // 注意判断容量是否为 0
            if(keyTable.count(key)!=0){    // key 已经在缓存中了
                auto it = keyTable[key];   // 下面的步骤和 get 函数中 else 部分基本相同
                int freq = it->freq;
                freqTable[freq].erase(it);
                if(freqTable[freq].size()==0){
                    freqTable.erase(freq);
                    if(minFreq==freq) minFreq++;
                }
                freqTable[freq+1].push_front(Node(key, value, freq+1));
                keyTable[key] = freqTable[freq+1].begin();
            }
            else{                          // key 不在缓存中
                if(keyTable.size()==this->capacity){   // 缓存容量已满
                    Node node = freqTable[minFreq].back();   // 通过 minFreq 找到使用最少的节点 back()
                    keyTable.erase(node.key);      // 删除使用最少的节点
                    freqTable[minFreq].pop_back();
                    if(freqTable[minFreq].empty()){
                        freqTable.erase(minFreq);
                    }
                }
                freqTable[1].push_front(Node(key, value, 1));
                keyTable[key] = freqTable[1].begin();
                minFreq = 1;   // minFreq 置为 1
            }
        }
    };
    
    /**
     * 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);
     */
    

    注意点:

    • 在 freqTable[freq] 对应的链表中插入节点时,都是插入链表头 (push_front());
    • 每次在 freqTable[freq] 对应的链表中删除节点后,都要判断 freqTable[freq] 是否为空,如果为空,则将 freq 从 freqTable 中删除,并在必要的情况下更新 minFreq。

    参考

    1、https://leetcode-cn.com/problems/lfu-cache/solution/ha-xi-shuang-xiang-lian-biao-lfuhuan-cun-by-realzz/
    2、https://leetcode-cn.com/problems/lfu-cache/solution/ha-xi-biao-shuang-xiang-lian-biao-java-by-liweiwei/

  • 相关阅读:
    ACM: POJ 1401 Factorial-数论专题-水题
    ACM:POJ 2739 Sum of Consecutive Prime Numbers-素数打表-尺取法
    ACM: HDU 1028 Ignatius and the Princess III-DP
    ACM: HDU 2563 统计问题-DFS+打表
    ACM: How many integers can you find-数论专题-容斥原理的简单应用+GCD
    ACM: Happy 2004-数论专题-因子求和-快速幂
    ACM:a^b%p-数论-快速幂-快速乘
    ACM: 强化训练-Beautiful People-最长递增子序列变形-DP
    POJ 1472 Instant Complexity 应该叫它编程题。。
    POJ 3393 Lucky and Good Months by Gregorian Calendar 模拟题
  • 原文地址:https://www.cnblogs.com/flix/p/13680008.html
Copyright © 2011-2022 走看看