LeetCode 每日一题 460. LFU缓存 平衡二叉树+哈希表实现 详细题解 C++描述

LeetCode 每日一题 460. LFU缓存

2020.04.05
难度 困难

题目

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

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

进阶:
你是否可以在 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

题解一:平衡二叉树+哈希表

  这题我们可以使用平衡二叉树和哈希表的组合来解决这个问题。我们这的平衡二叉树在C++中STL中的<set>代替,<set>的底层实现也是平衡二叉树。哈希表的话我们这里使用的unordered_map,这里使用unordered_map而不是map的原因是,unordered_map在内部实现了哈希表,查询速度非常快,但是建表的速度稍慢,适合查询较多的数据。
  下面我们来说具体的实现,首先我们这里需要实现的有LFUCache()的构造函数、get()方法、put()方法。我们这里首先需要一个结构体进行存储我们的各个结点的数据(使用频率、缓存的使用时间、键key、值value),这里我们使用一个Node结构体。在结构体内我们写了一个赋值函数Node(int _cnt, int _time, int _key, int _value),方便我们进行赋值,然后由于我们需要将Node存储进<set>中,所以我们需要重载<运算符,这里的<符号的运算规则采用最近最少使用原则进行运算,在代码中进行了更详细的规则解释。
  我们在LFUCache类中需要变量capacity 表示缓存容量,time 表示当前时间,unordered_map<int, Node> hash_map;hash_map用于存储key和Node,set<Node> S;用于存储结点,并且自动的从小到大排序,这就是我们类需要使用的四个变量。在构造函数LFUCache(int _capacity)中,我们只需要执行初始化操作即可。
  get(int key)方法的实现,我们只需要在hash_map中进行查找,如果查找到了,则返回值,并更新数据,没查找到则返回-1
  put(int key, int value)方法的实现,就是在hash_map中对key进行查找,如果查找到了,则更新数据(和get()中类似),如果没查到到,则需要判断容量是否满,如果满,则删除最近最少使用的结点即S的开始结点,然后再进行插入操作,如果容量没满,则直接进行插入操作。
下面贴上详细注释的解题代码,供大家参考:

struct Node {
    // cnt 表示缓存使用的频率,time 表示缓存的使用时间,key 表示缓存键, value 表示缓存的值
    int cnt, time, key, value;

    // 赋值函数
    Node(int _cnt, int _time, int _key, int _value){
        cnt = _cnt;
        time = _time;
        key = _key;
        value = _value;
    }
    // 重载运算符,因为这里使用set,需要对`<`运算符进行重载
    bool operator < (const Node& mid) const {
        // 返回的值为:如果比较的Node和当前的Node的使用频率一样,则返回比较两者的使用时间,如果当前时间小则返回1(当前的最近使用,最近最少使用原则),否则返回0;如果使用频率不一样,则返回两者时间的比值,即以cnt的大小作为判断。
        return cnt == mid.cnt ? time < mid.time : cnt < mid.cnt;
    }
};
class LFUCache {
    // capacity 表示缓存容量,time 表示当前时间
    int capacity, time;
    // 这里使用了unordered_map而不是map,因为unordered_map内部实现了哈希表,查找速度快,适用于查找多的结构
    // hash_map用于存储key和Node
    unordered_map<int, Node> hash_map;
    // S用于存储Node,并且自动的排序,从小到大
    set<Node> S;
public:
    LFUCache(int _capacity) {
        // 第一次调用时,进行初始化
        capacity = _capacity;
        time = 0;
        hash_map.clear();
        S.clear();
    }
    
    int get(int key) {
        // 如果容量为空,则一直无法存储,直接返回-1
        if (capacity == 0) 
            return -1;
        // 在hash_map中查找key
        auto it = hash_map.find(key);
        // 如果哈希表中没有键 key,返回 -1
        if (it == hash_map.end()) 
            return -1;
        // 如果查找到了key,则从哈希表中得到旧的缓存,这里的second即为unordered_map<int, Node> hash_map;中的第二个参数,Node类型,
        Node cache = it -> second;
        // 从平衡二叉树中删除旧的缓存
        S.erase(cache);
        // 将旧缓存更新,使用频次+1,时间为当前时间+1
        cache.cnt += 1;
        cache.time = ++time;
        // 将新缓存重新放入哈希表和平衡二叉树中
        S.insert(cache);
        it -> second = cache;
        return cache.value;
    }
    
    void put(int key, int value) {
        // 如果容量为0,则直接返回,即不操作
        if (capacity == 0) 
            return;
        // 在hash_map中查找Key
        auto it = hash_map.find(key);
        // 如果没查找到key,则进行插入
        if (it == hash_map.end()) {
            // 如果到达缓存容量上限
            if (hash_map.size() == capacity) {
                // 分别从哈希表和平衡二叉树中删除最近最少使用的缓存,S是按照最近最少使用进行排序的,所以第一个也就是最近最少使用的那个。
                hash_map.erase(S.begin() -> key);
                S.erase(S.begin());
            }
            // 创建新的缓存,使用频次初始为1,时间为当前时间+1,key,value即参数中的键值对
            Node cache = Node(1, ++time, key, value);
            // 将新缓存放入哈希表和平衡二叉树中
            hash_map.insert(make_pair(key, cache));
            S.insert(cache);
        }
        else {
            // 如果找到了key,则进行一个更新操作,和get()的更新操作类似
            Node cache = it -> second;
            S.erase(cache);
            cache.cnt += 1;
            cache.time = ++time;
            cache.value = value;
            S.insert(cache);
            it -> second = cache;
        }
    }
};

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

执行结果为:
在这里插入图片描述

发布了170 篇原创文章 · 获赞 884 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/qq_43422111/article/details/105322050