LeetCode 460. LFU Cache - 链表(Linked List)系列题30

esign and implement a data structure for a Least Frequently Used (LFU) cache.

Implement the LFUCache class:

  • LFUCache(int capacity) Initializes the object with the capacity of the data structure.
  • int get(int key) Gets the value of the key if the key exists in the cache. Otherwise, returns -1.
  • void put(int key, int value) Update the value of the key if present, or inserts the key if not already present. When the cache reaches its capacity, it should invalidate and remove the least frequently used key before inserting a new item. For this problem, when there is a tie (i.e., two or more keys with the same frequency), the least recently used key would be invalidated.

To determine the least frequently used key, a use counter is maintained for each key in the cache. The key with the smallest use counter is the least frequently used key.

When a key is first inserted into the cache, its use counter is set to 1 (due to the put operation). The use counter for a key in the cache is incremented either a get or put operation is called on it.

The functions get and put must each run in O(1) average time complexity.

Example 1:

Input
["LFUCache", "put", "put", "get", "put", "get", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [3], [4, 4], [1], [3], [4]]
Output
[null, null, null, 1, null, -1, 3, null, -1, 3, 4]

Explanation
// cnt(x) = the use counter for key x
// cache=[] will show the last used order for tiebreakers (leftmost element is  most recent)
LFUCache lfu = new LFUCache(2);
lfu.put(1, 1);   // cache=[1,_], cnt(1)=1
lfu.put(2, 2);   // cache=[2,1], cnt(2)=1, cnt(1)=1
lfu.get(1);      // return 1
                 // cache=[1,2], cnt(2)=1, cnt(1)=2
lfu.put(3, 3);   // 2 is the LFU key because cnt(2)=1 is the smallest, invalidate 2.
                 // cache=[3,1], cnt(3)=1, cnt(1)=2
lfu.get(2);      // return -1 (not found)
lfu.get(3);      // return 3
                 // cache=[3,1], cnt(3)=2, cnt(1)=2
lfu.put(4, 4);   // Both 1 and 3 have the same cnt, but 1 is LRU, invalidate 1.
                 // cache=[4,3], cnt(4)=1, cnt(3)=2
lfu.get(1);      // return -1 (not found)
lfu.get(3);      // return 3
                 // cache=[3,4], cnt(4)=1, cnt(3)=3
lfu.get(4);      // return 4
                 // cache=[3,4], cnt(4)=2, cnt(3)=3

Constraints:

  • 0 <= capacity <= 104
  • 0 <= key <= 105
  • 0 <= value <= 109
  • At most 2 * 105 calls will be made to get and put.

题目要设计一个 Least Frequently Used (LFU) Cache,即最不经常使用缓存,跟146. LRU Cache 一样是缓存技术的一种算法。LFU与LRU的不同在于当缓存满了时对要丢弃的数据的处理机制不一样,LFU是把最不经常使用也就是被访问的频率最小的那个单元丢弃,要是有多个单元的被访问频率是一样的,那就遵循LRU规则。

LFU的实现还是基于双向链表,由于是根据频率大小来选择要丢弃的数据单元并且同一频率下有可能有多个数据单元,因此要针对每个频率值维护一个链表。链表内的数据丢弃原则是基于LRU的,只要始终把最新的数据插入链表头,那么链表尾的数据就是least recently used的。

LFU接口函数:

  • LFUCache(int capacity) 初始化数据接口和相关变量。
  •         self.size = capacity:#缓存大小
            self.f2list = {}:#用于存储频率与其对应链表的映射关系                                                   
            self.k2node = {} #用于存储key与数据单元(节点)的映射关以便快速查找到节点。
            self.cnt = 0 # 用于记录当前缓存里数据单元的个数
            self.minFreq = 1 #用于追踪当前的最小频率,从1开始
            self.f2list[1] = self.create() #为频率1创建一个只有伪头节点的空链表
  • int get(int key) 给定一个key,如果存在则返回对应的value,如不存在返回-1。另外如果key存在说明其对应的数据单元又被访问了一次,节点访问频率值freq加1,这时要把该节点从freq对应的链表中删除,插入到freq+1对应的链表中(如不存在则要为freq+1先创造一个新链表)。还有如果被访问的节点的原频率就是最小频率,还需要判断是否要更新当前最小频率self.minFreq,要是freq对应的链表被删掉一个节点后变为空则self.minFreq加1,否则不变。
  • void put(int key, int value) 给定一对key-value,如key已经存在则更新key对应的value,并且其对应的数据单元又被访问了一次,需要做跟get()一样的处理。如key不存在则需插入新的key-value即在频率为1的链表中插入一个新节点,在插入一个新节点前需要判断缓存是否已满,如果慢则需要丢弃一个数据单元,最小频率值self.minFreq对应的链表尾部的那个节点就是要删除的节点,将其删除。然后创建一个新节点插入到频率为1的链表中,self.k2node增加一对key-node,节点个数self.cnt加1,最小频率self.minFreq复位成1。

 

class ListNode:
    def __init__(self, key = None, val = None):
        self.key = key
        self.val = val
        self.freq = 1
        self.prev = None
        self.next = None
        
class LFUCache:

    def __init__(self, capacity: int):
        self.size = capacity
        self.f2list = {}
        self.k2node = {}
        self.cnt = 0
        self.minFreq = 1
        self.f2list[1] = self.create()

    def get(self, key: int) -> int:
        if self.size == 0 or key not in self.k2node :
            return -1
        node = self.k2node[key]
        self.delete(node)
        head = self.f2list[node.freq]
        if self.minFreq == node.freq and self.isEmpty(head):
            self.minFreq += 1
        node.freq += 1
        if node.freq not in self.f2list:
            self.f2list[node.freq] = self.create()
        head = self.f2list[node.freq]
        self.insert(head, node)
        return node.val
        
    def put(self, key: int, value: int) -> None:
        if self.size == 0:
            return
        
        if key in self.k2node:
            node = self.k2node[key]
            node.val = value
            self.get(key)
            return
        
        if self.cnt == self.size:
            head = self.f2list[self.minFreq]
            self.k2node.pop(head.prev.key)
            self.delete(head.prev)
            self.cnt -= 1
            
        node = ListNode(key, value)
        self.minFreq = 1
        head = self.f2list[1]
        self.insert(head, node)
        self.k2node[key] = node
        self.cnt += 1
        
    def create(self):
        head = ListNode()
        head.next = head
        head.prev = head
        return head

    def insert(self, head, node):
        node.next = head.next
        head.next.prev = node
        head.next = node
        node.prev = head
    
    def delete(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev
        
    def isEmpty(self, head):
        return head.next == head

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值