题目描述:
Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the following operations: get
and set
.
get(key)
- Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
set(key, value)
- Set or insert the value if the key is not already present. When the cache reaches its capacity, it should invalidate the least frequently used item before inserting a new item. For the purpose of this problem, when there is a tie (i.e., two or more keys that have the same frequency), the least recently used key would be evicted.
Follow up:
Could you do both operations in O(1) time complexity?
Example:
LFUCache cache = new LFUCache( 2 /* capacity */ ); cache.set(1, 1); cache.set(2, 2); cache.get(1); // returns 1 cache.set(3, 3); // evicts key 2 cache.get(2); // returns -1 (not found) cache.get(3); // returns 3. cache.set(4, 4); // evicts key 1. cache.get(1); // returns -1 (not found) cache.get(3); // returns 3 cache.get(4); // returns 4
题目大意:
为“最不常使用缓存”(LFU cache)设计实现数据结构。应当支持get和set操作。
get(key) - 如果存在key,返回其对应的value,否则返回-1。
set(key, value) - 如果不存在key,新增value;否则替换原始value。当缓存容量满时,应当将最不常使用的项目移除。如果存在使用频度相同的多个项目,则移除最近最少使用(Least Recently Used)的项目。
进一步思考:
能否在O(1)时间完成操作?
解题思路:
双向链表(Doubly Linked List) + 哈希表(Hash Table)
首先定义双向链表节点:KeyNode(Key节点)与FreqNode(频度节点)。
KeyNode中保存key(键),value(值),freq(频度),prev(前驱),next(后继) FreqNode中保存freq(频度)、prev(前驱)、next(后继)、first(指向最新的KeyNode),last(指向最老的KeyNode)
在数据结构LFUCache中维护如下属性:
capacity:缓存的容量 keyDict:从key到KeyNode的映射 freqDict:从freq到FreqNode的映射 head:指向最小的FreqNode
整体数据结构设计如下图所示:
head --- FreqNode1 ---- FreqNode2 ---- ... ---- FreqNodeN | | | first first first | | | KeyNodeA KeyNodeE KeyNodeG | | | KeyNodeB KeyNodeF KeyNodeH | | | KeyNodeC last KeyNodeI | | KeyNodeD last | last
LFUCache操作实现如下:
set(key, value):
如果capacity为0,忽略当前操作,结束 如果keyDict中包含key,则替换其value,更新节点频度,结束 否则,如果当前keyDict的长度 == capcity,移除head.last(频度最低且最老的KeyNode) 新增KeyNode(key, value),加入keyDict,并更新freqDict
get(key):
若keyDict中包含key,则更新节点频度,返回对应的value 否则,返回-1
节点频度的更新:
从keyDict中找到对应的KeyNode,然后通过KeyNode的freq值,从freqDict找到对应的FreqNode 如果FreqNode的next节点不等于freq + 1,则在其右侧插入一个值为freq + 1的新FreqNode节点 将KeyNode的freq值+1后,从当前KeyNode链表转移到新的FreqNode对应的KeyNode链表 如果KeyNode移动之后,原来的FreqNode对应的KeyNode链表为空,则删除原来的FreqNode 在操作完毕后如果涉及到head的变更,则更新head
Python代码:
class KeyNode(object):
def __init__(self, key, value, freq = 1):
self.key = key
self.value = value
self.freq = freq
self.prev = self.next = None
class FreqNode(object):
def __init__(self, freq, prev, next):
self.freq = freq
self.prev = prev
self.next = next
self.first = self.last = None
class LFUCache(object):
def __init__(self, capacity):
"""
:type capacity: int
"""
self.capacity = capacity
self.keyDict = dict()
self.freqDict = dict()
self.head = None
def get(self, key):
"""
:type key: int
:rtype: int
"""
if key in self.keyDict:
keyNode = self.keyDict[key]
value = keyNode.value
self.increase(key, value)
return value
return -1
def set(self, key, value):
"""
:type key: int
:type value: int
:rtype: void
"""
if self.capacity == 0:
return
if key in self.keyDict:
self.increase(key, value)
return
if len(self.keyDict) == self.capacity:
self.removeKeyNode(self.head.last)
self.insertKeyNode(key, value)
def increase(self, key, value):
"""
Increments the freq of an existing KeyNode<key, value> by 1.
:type key: str
:rtype: void
"""
keyNode = self.keyDict[key]
keyNode.value = value
freqNode = self.freqDict[keyNode.freq]
nextFreqNode = freqNode.next
keyNode.freq += 1
if nextFreqNode is None or nextFreqNode.freq > keyNode.freq:
nextFreqNode = self.insertFreqNodeAfter(keyNode.freq, freqNode)
self.unlinkKey(keyNode, freqNode)
self.linkKey(keyNode, nextFreqNode)
def insertKeyNode(self, key, value):
"""
Inserts a new KeyNode<key, value> with freq 1.
:type key: str
:rtype: void
"""
keyNode = self.keyDict[key] = KeyNode(key, value)
freqNode = self.freqDict.get(1)
if freqNode is None:
freqNode = self.freqDict[1] = FreqNode(1, None, self.head)
if self.head:
self.head.prev = freqNode
self.head = freqNode
self.linkKey(keyNode, freqNode)
def delFreqNode(self, freqNode):
"""
Delete freqNode.
:rtype: void
"""
prev, next = freqNode.prev, freqNode.next
if prev: prev.next = next
if next: next.prev = prev
if self.head == freqNode: self.head = next
del self.freqDict[freqNode.freq]
def insertFreqNodeAfter(self, freq, node):
"""
Insert a new FreqNode(freq) after node.
:rtype: FreqNode
"""
newNode = FreqNode(freq, node, node.next)
self.freqDict[freq] = newNode
if node.next: node.next.prev = newNode
node.next = newNode
return newNode
def removeKeyNode(self, keyNode):
"""
Remove keyNode
:rtype: void
"""
self.unlinkKey(keyNode, self.freqDict[keyNode.freq])
del self.keyDict[keyNode.key]
def unlinkKey(self, keyNode, freqNode):
"""
Unlink keyNode from freqNode
:rtype: void
"""
next, prev = keyNode.next, keyNode.prev
if prev: prev.next = next
if next: next.prev = prev
if freqNode.first == keyNode: freqNode.first = next
if freqNode.last == keyNode: freqNode.last = prev
if freqNode.first is None: self.delFreqNode(freqNode)
def linkKey(self, keyNode, freqNode):
"""
Link keyNode to freqNode
:rtype: void
"""
firstKeyNode = freqNode.first
keyNode.prev = None
keyNode.next = firstKeyNode
if firstKeyNode: firstKeyNode.prev = keyNode
freqNode.first = keyNode
if freqNode.last is None: freqNode.last = keyNode
# Your LFUCache object will be instantiated and called as such:
# obj = LFUCache(capacity)
# param_1 = obj.get(key)
# obj.set(key,value)
下面的Java代码实现摘自LeetCode Discuss
原文链接:https://discuss.leetcode.com/topic/69137/java-o-1-accept-solution-using-hashmap-doublelinkedlist-and-linkedhashset/5
Java代码:
public class LFUCache {
Node head = null;
final int capacity;
Map<Integer, Integer> valueMap;
Map<Integer, Node> nodeMap;
public LFUCache (int capacity) {
this.capacity = capacity;
valueMap = new HashMap<>(this.capacity, 1f);
nodeMap = new HashMap<>(this.capacity, 1f);
}
public int get(int key) {
if (valueMap.containsKey(key)) increase(key, valueMap.get(key));
return valueMap.getOrDefault(key, -1);
}
private void increase(int key, int value) {
Node node = nodeMap.get(key);
node.keys.remove(key);
if (Objects.isNull(node.next)) node.next = new Node(node, null, 1 + node.count, key);
else if (node.next.count == node.count + 1) node.next.keys.add(key);
else node.next = node.next.prev = new Node(node, node.next, node.count + 1, key);
nodeMap.put(key, node.next);
valueMap.put(key, value);
if (node.keys.isEmpty()) remove(node);
}
private void remove(Node node) {
if (head == node) head = node.next;
else node.prev.next = node.next;
if (Objects.nonNull(node.next)) node.next.prev = node.prev;
}
public void set(int key, int value) {
if (0 == this.capacity) return;
if (valueMap.containsKey(key)) {
increase(key, value);
} else {
if (valueMap.size() == this.capacity) remove();
valueMap.put(key, value);
add(key);
}
}
private void add(int key) {
if (Objects.isNull(head)) head = new Node(null, null, 1, key);
else if (head.count == 1) head.keys.add(key);
else head = head.prev = new Node(null, head, 1, key);
nodeMap.put(key, head);
}
private void remove() {
if (Objects.isNull(head)) return;
int oldest = head.keys.iterator().next();
head.keys.remove(oldest);
if (head.keys.isEmpty()) remove(head);
nodeMap.remove(oldest);
valueMap.remove(oldest);
}
class Node {
public Node prev, next;
public final int count;
public LinkedHashSet<Integer> keys = new LinkedHashSet<>();
public Node(Node prev, Node next, int count, int key) {
this.prev = prev;
this.next = next;
this.count = count;
keys.add(key);
}
}
}
/**
* Your LFUCache object will be instantiated and called as such:
* LFUCache obj = new LFUCache(capacity);
* int param_1 = obj.get(key);
* obj.set(key,value);
*/
本文链接:http://bookshadow.com/weblog/2016/11/22/leetcode-lfu-cache/
请尊重作者的劳动成果,转载请注明出处!书影博客保留对文章的所有权利。
Andrew 发布于 2016年11月25日 01:44 #
http://massivealgorithms.blogspot.ca/2016/11/leetcode-460-lfu-cache.html
在线疯狂 发布于 2016年11月25日 10:00 #
链接指向的这篇博文中,第一部分转载自本文,方便的话请帮忙留言告知注明出处,谢谢 :)
阿狸不理你 发布于 2016年11月29日 01:24 #
这里有bug,在set 函数里面,size 检查是不对的
在线疯狂 发布于 2016年11月29日 09:33 #
能否详细描述一下?
多拉a萌 发布于 2016年12月13日 21:37 #
我觉得用 最小堆来保存 频率会更好
在线疯狂 发布于 2016年12月13日 22:11 #
使用堆也可以,但siftUp / siftDown的时间复杂度为O(log n)
夏风艾利克斯 发布于 2017年1月3日 05:52 #
Java版本的set是有bug,应该先检查capacity再put。
在线疯狂 发布于 2017年1月3日 16:53 #
感谢提醒,已经更正!