[Leetcode] 460. LFU Cache 解题报告

本文详细解析了LeetCode中460题LFU Cache的设计与实现,介绍了如何在O(1)时间复杂度内通过哈希表、双向链表和迭代器来管理数据,包括get和put操作,以及当缓存满时如何删除最不常使用的元素。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the following operations: get and put.

get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value) - Set or insert the value if the key is not already present. When the cache reaches its capacity, it should invalidate the least frequently used item before inserting a new item. For the purpose of this problem, when there is a tie (i.e., two or more keys that have the same frequency), the least recently used key would be evicted.

Follow up:
Could you do both operations in O(1) time complexity?

Example:

LFUCache cache = new LFUCache( 2 /* capacity */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // returns 1
cache.put(3, 3);    // evicts key 2
cache.get(2);       // returns -1 (not found)
cache.get(3);       // returns 3.
cache.put(4, 4);    // evicts key 1.
cache.get(1);       // returns -1 (not found)
cache.get(3);       // returns 3
cache.get(4);       // returns 4

思路

记不清和前面哪道题目比较类似了,不过确实还是比较难的一道题目,O(1)的时间复杂度主要是通过哈希表、双向链表以及记录迭代器来实现的,下面我们分别详细介绍一下。

1)由于需要在O(1)的时间复杂度内通过key得到value值,所以需要建立key到value的哈希映射。但是由于每个key还对应着一个访问频率,所以为了简便起见,我们将值和访问频率组合成为一个pair {value, freq},定义一个哈希表m。

2)由于要在O(1)的时间内通过访问频率来定位需要删除的key值,所以我们定义一个从访问频率到key的哈希映射。但是由于同一个访问频率可能对应着多个key值,所以在该哈希表中,我们将value定义为一个双向列表。该哈希表名为fm。

3)由于我们需要在O(1)的时间内,对某一key值的访问频率进行修改,并且还不能破坏同一频率内的多个key值的有序性,所以我们还需要定义一个从key值到该key在访问频率哈希表中的迭代器,这样当有了key值之后,我们就可以直接定位到该值在频率哈希表中需要删除的迭代器,从而在O(1)的时间内将其删除。而由于双向列表的有序性,又可以保证在频率相同的情况下,删除掉最久没有用的那个key。

如果上面这三个关键的数据结构理解清楚了,那么这几个函数的实现就比较简单了:

1)get函数:调用一个get之后其访问频率要提高,所以我们首先修改fm,将该key从fm的原有访问频率对应的list中删除掉,并且加入到fm中新访问频率所对应的list中。

2)put函数:首先利用get函数试图访问该key值,如果该key值已经存在了,则直接修改value值即可(注意对访问频率的修改已经在get函数中完成了)。那么如果该key值还不存在呢?我们就首先检查是否超过容量了,如果是,则从哈希表m中删除掉最小频率所对应的双向列表的头结点对应的key值(头结点是同一访问频率中,最久没有被使用的),从fm中删除掉该key值,再从mIter中删除掉对应迭代器。最后就是添加新的item了。

我在下面的代码片段中给出了详细解释。有问题的读者欢迎在下面留言。

代码

class LFUCache {
public:
    LFUCache(int capacity) {
        cap = capacity;
        size = 0;
    }
    
    int get(int key) {
        if(m.count(key) == 0) {
            return -1;
        }        
        fm[m[key].second].erase(mIter[key]);        // frequency will increase, so erase it from fm
        m[key].second++;                            // increase the frequency
        fm[m[key].second].push_back(key);           // add it to the new position in fm (at the end)
        mIter[key] = --fm[m[key].second].end();     // the iterator should be the last in fm
        if(fm[minFreq].size() == 0) {
            ++minFreq;
        }
        return m[key].first;
    }
    
    void put(int key, int value) {
        if(cap <= 0) {
            return;
        }        
        int storedValue = get(key);                     // check whether this key exists
        if(storedValue != -1) {
            m[key].first = value;                       // modify the value (note the private data has been updated already)
            return;
        }
        if(size >= cap) {
            m.erase(fm[minFreq].front());
            mIter.erase(fm[minFreq].front());
            fm[minFreq].pop_front();
            --size;
        }
        m[key] = {value, 1};                            // add the new item
        fm[1].push_back(key);
        mIter[key] = --fm[1].end();
        minFreq = 1;
        size++;
    }
private:
    int cap;
    int size;
    int minFreq;                                        // minFreq is the smallest frequency so far
    unordered_map<int, pair<int, int>> m;               // key to {value,freq};
    unordered_map<int, list<int>::iterator> mIter;      // key to list iterator, stores the key's position in the linked list;
    unordered_map<int, list<int>> fm;                   // freq to key list;
};

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值