【Leetcode】460. LFU Cache

题目地址:

https://leetcode.com/problems/lfu-cache/

实现一个Least Frequently Used (LFU) cache。它是一个类似HashMap的数据结构,存的是key-value pair,key唯一,并可以做三种操作:
1、初始化,它有一个最大容量;
2、int get(int key),得到key对应的value,如果key不存在则返回 − 1 -1 1
3、void put(int key, int value),如果key不存在,则添加该key-value pair,并且如果当前已经存了最大容量个元素了,就需要将cache里使用次数最少的元素删掉,如果有多个,则把最近使用时间最久的元素删掉,这里的“使用”的含义是这个元素被get过或put过,都算;如果key存在,则更新其value。

思路是HashMap + 双链表。开两个HashMap,一个存key和Node的对应关系,其中Node里存key对应的value以及该key被使用的次数,另一个存使用次数和该使用次数对应的所有Node所组成的链表,之所以要用链表来存,是因为要在使用次数相同的情况下删除最近使用时间最久的那个Node,可以借助链表来实现,链表头存最新的Node,链表尾存最旧的Node。并且,由于我们要找使用次数最小的那个链表,所以我们再维护一个变量minFreq专门存当前使用次数最少是多少次。

我们先考虑某个node被使用过一次之后,应该怎样处理它。我们开个函数叫update(Node node)专门做这件事。其实现如下:
因为其freq会增加,所以我们先找到其freq对应的链表,然后将其在链表中删除,接着看一下minFreq是否要被更新,其被更新当且仅当当前使用次数为minFreq的节点都不存在了,即这个链表空了,并且它刚好就是对应的minFreq。如果是这样,就将minFreq增加 1 1 1(因为被update的节点的使用次数会等于minFreq加 1 1 1),再接着将node的使用次数加 1 1 1,再加入这个使用次数对应的链表的头部。当然如果该使用次数的链表不存在,则还需要将其建出来。

三个操作分别如下实现:
1、初始化两个HashMap,并存一下最大容量值。最好先在frequency map里先存一个使用次数 1 1 1对应的链表,因为添加新值的时候这个链表是一定会被用到的;
2、get的时候,先从key找到对应的node(找不到则直接返回 − 1 -1 1),然后将其更新(执行update操作),最后返回该node里存的value值即可;
3、put的时候,如果最大容量是 0 0 0那就什么也不用做了,直接返回;接着先看一下key是否存在,如果存在,则将其对应的Node找出来,更新其value,并执行update;如果不存在,说明需要增加新Node了,此时要看一下是否达到了容量上限,如果达到了,则要执行删除Least Frequently Used元素的操作(该操作这样执行,先找到minFreq对应的链表,然后删掉其表尾node,并且将该node在key-Node的HashMap里也删掉。如果该链表空了,则说明minFreq的元素不存在了,但是这里事实上不需要更新minFreq,因为执行了删除Least Frequently Used元素的操作一定是因为有新元素进来,它的freq是 1 1 1,所以minFreq一定会变成 1 1 1,这可以在后面的逻辑里写上),删除完之后,将新节点new出来,加入key-Node的HashMap中,并将其加入使用次数为 1 1 1的链表表头,并且重置minFreq为 1 1 1

代码如下:

class LFUCache {
 private:
  struct Node {
    int key, val, freq;
    Node *prev, *next;
    Node(int key, int val)
        : key(key), val(val), prev(nullptr), next(nullptr), freq(1) {}
  };
  struct LinkedList {
    Node *head, *tail;
    int sz;
    LinkedList() : sz(0) {
      head = new Node(0, 0);
      tail = new Node(0, 0);
      head->next = tail;
      tail->prev = head;
    }

    void addFirst(Node *node) {
      node->prev = head;
      node->next = head->next;
      head->next = node;
      node->next->prev = node;
      sz++;
    }

    void remove(Node *node) {
      if (!sz) return;
      node->prev->next = node->next;
      node->next->prev = node->prev;
      sz--;
    }

    Node *removeLast() {
      if (!sz) return nullptr;
      auto *res = tail->prev;
      remove(res);
      return res;
    }
  };

  unordered_map<int, Node *> nodemp;
  unordered_map<int, LinkedList *> freq;
  int cap, minFreq;

  void update(Node *node) {
    auto *list = freq[node->freq];
    list->remove(node);
	// 如果该node是minFreq对应的唯一节点,则需要更新minFreq
    if (node->freq == minFreq && !list->sz) minFreq++;
    node->freq++;
    if (!freq.count(node->freq)) freq[node->freq] = new LinkedList();
    freq[node->freq]->addFirst(node);
  }
  
  // 这个是为了删除链表尾,即最近使用时间最久的元素
  void removeOldest() {
    auto *list = freq[minFreq];
    auto *node = list->removeLast();
    nodemp.erase(node->key);
  }

 public:
  LFUCache(int capacity) {
	// 使用次数为1的链表一开始就添进去,因为这个链表是一定会被用到的(除非cap = 0)
    freq[1] = new LinkedList();
    cap = capacity;
    minFreq = 0;
  }

  int get(int key) {
    if (!nodemp.count(key)) return -1;
    auto *node = nodemp[key];
    update(node);
    return node->val;
  }

  void put(int key, int value) {
    // 尤其要注意特判这个,否则nodeMap为空的时候会触发removeOldest操作,
    // 会导致NullPointerException
    if (!cap) return;
    if (nodemp.count(key)) {
      auto *node = nodemp[key];
      node->val = value;
      update(node);
      return;
    }

    if (nodemp.size() == cap) removeOldest();
    auto *node = new Node(key, value);
    nodemp[key] = node;
    freq[1]->addFirst(node);
    minFreq = 1;
  }
};

所有操作时间复杂度 O ( 1 ) O(1) O(1),空间 O ( n ) O(n) O(n) n n n是最大容量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值