思路:

题目的主要信息:

  • 实现LFU的set与get函数,且复杂度为O(1)
  • 每次调用这两个函数会给一个频率赋值,超出长度则移除频率最少的,若有频率相同,则移除访问时间最早的

方法一:平衡二叉树+哈希表

哈希表有非常好的之间访问,可以达成O(1),而经过算术符号重载后的平衡二叉树,能够找到最近最少用的放在树根节点。

具体做法:

使用STL自带的map作为哈希表,set作为平衡二叉树。 对于 get函数,只需要查看一下哈希表mp是否有key这个键即可,有的话需要同时更新哈希表和集合中该缓存的使用频率cnt以及使用时间time,否则返回 -1。(时间最大等于操作数n,所以不用担心很大) 而对于put函数,首先需要查看mp中是否已有对应的键值。如果有的话不用其他操作,仅需要更新缓存的 频率和时间值。如果没有的话相当于是新插入一个缓存,这时候需要先查看是否达到缓存容量zui'da'z capacity,如果达到了的话,需要删除最近最少使用的缓存,即平衡二叉树中最左边的结点,同时删除 mp中对应的索引,最后向mp和二叉树中插入新的缓存信息即可。

class Solution {
public:
      struct Node {
          int cnt; //频率
          int time; //访问时间
          int key;
          int value;
          Node(int _cnt, int _time, int _key, int _value):cnt(_cnt), time(_time), key(_key), value(_value){}
          //将 cnt(使用频率)作为第一关键字,time(最近一次使用的时间)作为第二关键字进行比较
          bool operator< (const Node& rhs) const { //重载比较符号<
              return cnt == rhs.cnt ? time < rhs.time : cnt < rhs.cnt;
          }
      };
      int capacity; //全局变量表示当前LFU容量
      int time; //表示当前的次数
      unordered_map<int, Node> mp; //哈希表
      set<Node> S; //平衡二叉树
      int get(int key) {
        if (capacity == 0) 
            return -1;
        auto it = mp.find(key);
        // 如果哈希表中没有键 key,返回 -1
        if (it == mp.end()) 
            return -1;
        // 从哈希表中得到旧的缓存
        Node cache = it->second;
        // 从平衡二叉树中删除旧的缓存
        S.erase(cache);
        // 将旧缓存更新
        cache.cnt += 1;
        cache.time = ++time;
        // 将新缓存重新放入哈希表和平衡二叉树中
        S.insert(cache);
        it -> second = cache;
        return cache.value;
    }    
    void set(int key, int value) {
        if (capacity == 0) 
            return;
        auto it = mp.find(key);
        if (it == mp.end()) {
            // 如果到达缓存容量上限
            if (mp.size() == capacity) {
                // 从哈希表和平衡二叉树中删除最近最少使用的缓存
                mp.erase(S.begin() -> key);
                S.erase(S.begin());
            }
            // 创建新的缓存
            Node cache = Node(1, ++time, key, value);
            // 将新缓存放入哈希表和平衡二叉树中
            mp.insert(make_pair(key, cache));
            S.insert(cache);
        }
        else {
            // 这里和 get() 函数类似
            Node cache = it -> second;
            S.erase(cache);
            cache.cnt += 1;
            cache.time = ++time;
            cache.value = value;
            S.insert(cache);
            it -> second = cache;
        }
    }
      vector<int> LFU(vector<vector<int> >& operators, int k) {
         vector<int> res;  //记录输出
         capacity = k; 
         for (int i = 0; i < operators.size(); i++) {
             if (operators[i][0] == 1)
                 set(operators[i][1], operators[i][2]);
             else if (operators[i][0] == 2) {
                 res.push_back(get(operators[i][1]));
             }
         }
       return res;       
    }
};

复杂度分析:

  • 时间复杂度:O(nlgk),get时间复杂度O(logk),put 时间复杂度O(logk),一共n步操作,k的容量
  • 空间复杂度:O(k),哈希表和平衡二叉树不会存放超过缓存容量的键值对

方法二:双哈希表

具体做法:

需要建立一个双向量表及两个哈希表,链表结点存储频率、key及value。第一个哈希表建立链表与频率的映射,旨在能O(1)找到最小频率;第二个哈希表建立键值key到第一个哈希表的映射,旨在能O(1)找到key对应的那组数据。 对于get函数,直接访问哈希表即可,但是访问后要更新频率; 对于set函数,需要容量未满,则直接加入,若是满了则通过第一个哈希表剔出频率最低的结点,最后要更新结点频率为1。 图片说明 以下代码使用了C++ STL自带的list模仿双向链表。

class Solution {
public:
    //用list模拟双向链表
    unordered_map<int, list<vector<int> > > freq_mp; //频率到双向链表的哈希表
    unordered_map<int, list<vector<int> > ::iterator> mp; //key到双向链表迭代器的哈希表
    int min_fre = 0; //记录当前最小频次
    int capacity; //记录缓存剩余容量
    void update(list<vector<int> >::iterator iter, int key, int value) { //调用函数时更新频率
        int num = (*iter)[0];   //找到key
        freq_mp[num].erase(iter); 
        if(freq_mp[num].empty()) { 
            freq_mp.erase(num); 
            if(min_fre == num) min_fre++; //若当前频率为最小,最小频率加一
        }
        freq_mp[num + 1].push_front({num + 1, key, value}); //插入频率加一的双向链表表头,链表中对应:频率 key value
        mp[key] = freq_mp[num + 1].begin(); 
    }
    void set(int key, int value) {
        auto it = mp.find(key); 
        if(it != mp.end())
            update(it->second, key, value); //更新频率
        else { 
            if(capacity == 0) { //满容量则取频率最低的键
                int oldnum = freq_mp[min_fre].back()[1]; 
                freq_mp[min_fre].pop_back(); 
                if(freq_mp[min_fre].empty()) freq_mp.erase(min_fre); 
                mp.erase(oldnum); 
            }
            else capacity--; //若有空闲则直接加入,容量减1
            min_fre = 1; //最小频率置为1
            freq_mp[1].push_front({1, key, value}); //在频率为1的双向链表表头插入该键
            mp[key] = freq_mp[1].begin(); 
        }
    }
    int get(int key) {
        auto it = mp.find(key);
        if(it != mp.end()) { 
            auto iter = it->second; 
            int value = (*iter)[2]; 
            update(iter, key, value); //更新频率
            return value; 
        }
        else 
            return -1; //找不到返回-1
    }
    vector<int> LFU(vector<vector<int> >& operators, int k) {
         vector<int> res;  //记录输出
         capacity = k; 
         for (int i = 0; i < operators.size(); i++) {
             if (operators[i][0] == 1)
                 set(operators[i][1], operators[i][2]);
             else if (operators[i][0] == 2) {
                 res.push_back(get(operators[i][1]));
             }
         }
       return res;       
    }
};

复杂度分析:

  • 时间复杂度:O(n),取决于操作数n
  • 空间复杂度:O(k),取决于缓存容量k