LRU and LFU
2025, Mar 06
LFU
题目链接 :https://leetcode.cn/problems/lfu-cache/description/
LFU是按照使用频率对kv对排序,当cache满时,淘汰最不经常使用的kv对。
- 需要维护n个频次的双向链表
- 需要维护一个key_to_node的哈希表,key标识key,value标识对应的双向链表的节点
- 需要维护一个freq_to_head的哈希表,key标识频次,value标识对应频次的双向链表的头节点
- 维护一个min_freq,表示最小频次,当新插入kv对时,更新min_freq = 1,当最小的频次的链表为空时,min_freq++,这样可以通过freq_to_head[min_freq]来获取最小频次的链表头节点,然后删除freq_to_head[min_freq]->pre即可实现cache容量写满时的淘汰操作。
class LFUCache { private: struct ListNode{ int key; int val; int freq; ListNode *pre, *next; ListNode(int k, int v){ key = k; val = v; freq = 1; } }; unordered_map<int, ListNode*> freq_to_dummy; unordered_map<int, ListNode*> key_to_Node; int capacity; int min_freq; // 获取key对应节点的value // 如果不存在,返回nullptr // 如果存在,则将key从原来的频次链表中删除,删除时需要注意如果当前节点是原频次链表的最后一个节点,需要删除整个频次链表,并delete对应的dummy节点,将min_freq++ // 然后将当前节点插入到新的频次链表的头部,并返回对应node的节点 ListNode* get_node(int key){ auto it = key_to_Node.find(key); if(it == key_to_Node.end()){ return nullptr; } ListNode* node = it->second; remove_node(node); ListNode* now_dummy = freq_to_dummy[node->freq]; if(now_dummy->pre == now_dummy){ freq_to_dummy.erase(node->freq); delete now_dummy; if(min_freq == node->freq){ min_freq++; } } push_front(++node->freq, node); return node; } // 当push_front时,需要判断是否需要新建一个新的链表 ListNode* new_list(){ ListNode *dummy = new ListNode(0, 0); dummy->next = dummy; dummy->pre = dummy; return dummy; } // 向当前freq的链表的头部插入节点 void push_front(int freq, ListNode* node){ auto it = freq_to_dummy.find(freq); ListNode *now_dummy; if(it == freq_to_dummy.end()){ auto new_dummy = new_list(); freq_to_dummy[freq] = new_dummy; } now_dummy = freq_to_dummy[freq]; node->pre = now_dummy; node->next = now_dummy->next; node->next->pre = node; node->pre->next = node; } // 从链表中逻辑删除节点 void remove_node(ListNode* node){ node->pre->next = node->next; node->next->pre = node->pre; } public: LFUCache(int capacity) { this->capacity = capacity; } int get(int key) { auto node = get_node(key); return node == nullptr ? -1 : node->val; } // put时如果存在,则直接更新value,返回 // 如果不存在,则判断是否需要淘汰,如果当前节点总数已经等于capacity,则表示需要淘汰 // 淘汰首先根据min_freq找到对应的链表头节点,然后删除该节点的pre(因为在get_node的逻辑中保证频次链表中只有一个dummy节点时,整个频次会被删除,所以dummy的pre一定是存在的),如果删除后链表只有一个dummy,则删除该链表 // 最后由于新插入的节点freq都为1,所以更新min_freq为1,然后插入freq为1的链表的头部 void put(int key, int value) { auto node = get_node(key); if(node != nullptr){ node->val = value; return; } if(key_to_Node.size() == capacity){ auto dummy = freq_to_dummy[min_freq]; auto to_del_node = dummy->pre; key_to_Node.erase(to_del_node->key); remove_node(to_del_node); delete to_del_node; if(dummy->pre == dummy){ freq_to_dummy.erase(min_freq); delete dummy; } } key_to_Node[key] = node = new ListNode(key, value); push_front(1, node); min_freq = 1; } };