Leetcode146. LRU Cache
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and put.
get(key)
- Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value)
- Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.
The cache is initialized with a positive capacity.
Follow up:
Could you do both operations in O(1) time complexity?
Example:
LRUCache cache = new LRUCache( 2 /* capacity */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.put(4, 4); // evicts key 1
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4
思路分析
当存满的时候删除最远一次操作过的元素:用一个链表,每
put
一个元素,就把它加到链表尾部。如果get
某个元素,就把这个元素移动到链表尾部。当存满的时候,就把链表头的元素删除。(这里加到链表头部和尾部都是可以的,具体实现基本相同)
即对于链表的操作有:定位头尾节点,插入节点,找到某个节点,移动节点到尾结点,删除头节点。
①使用一个dummyHead
,也就是哨兵节点,不存数据,始终指向头结点,这样存满删除链表头元素的时间复杂度为O(1)
②同样的使用一个尾指针,这样到达链表尾部的时间复杂度为O(1)
③使用hashmap
,找到某个节点的时间复杂度为O(1)
④我们知道插入节点无需知道节点的prev
,但是删除节点的操作要将该节点的前驱节点的next
指向该节点的next
,即cur.prev->next = cur->next
,所以我们必须要记录每个节点的前驱节点,所以这里使用循环链表。
使用双向链表的一个好处是不需要额外信息删除一个节点,同时可以在常数时间内从头部或尾部插入删除节点。
在双向链表实现中,使用一个伪头部和伪尾部标记界限,这样在更新的时候就不需要检查是否是 null 节点。
定义节点类
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode(int key, int value) {
this.key = key;
this.value = value;
}
}
构建双向链表类,注意链表操作的时间复杂度需要为O(1)。
class DoubleList {
private DLinkedNode head, tail; // 头尾虚节点
public DoubleList() {//构造函数来初始化有两个节点的循环链表
head = new DLinkedNode(0, 0);
tail = new DLinkedNode(0, 0);
head.next = tail;
tail.prev = head;
head.prev = null;
tail.next = null;
}
//添加节点到末尾,先确定Node的前后,然后修改前后节点的指针
public void addNode(DLinkedNode node) {
node.prev = tail.prev;
node.next = tail;
tail.prev.next = node;
tail.prev = node;
}
//删除链表中的节点
public void removeNode(DLinkedNode node){
node.next.prev = node.prev;
node.prev.next = node.next;
}
//将某个确定的元素移到链表尾部
public void moveToTail(DLinkedNode node){
removeNode(node);
addNode(node);
}
//删除链表第一个节点并返回该节点
public DLinkedNode removeFirst(){
DLinkedNode res = head.next;
removeNode(res);
return res;
}
}
定义并实现LRU类,结合双向链表与哈希表。
class LRUCache {
private int capacity = 0;
private HashMap<Integer, DLinkedNode> map = new HashMap<>();
private DoubleList list = new DoubleList();
//构造函数
public LRUCache(int capacity) {
this.capacity = capacity;
}
//get方法:返回元素值并把这个元素移动到链表尾部。
public int get(int key) {
if (map.containsKey(key)) {
DLinkedNode node = map.get(key);
list.moveToTail(node);
return node.value;
} else {
return -1;
}
}
//put方法
//(一)如果key已经存在,则修改对应key的value,将节点移动到末尾。
//(二)key不存在:新建节点,如果capacity满,则删除表头的节点,并将map中的key删除。将新节点插入到尾部,并在map中新建映射。
public void put(int key, int value) {
if (map.containsKey(key)) {
DLinkedNode node = map.get(key);
node.value = value;
list.moveToTail(node);
} else {
DLinkedNode node = new DLinkedNode(key,value);
if (capacity == map.size()) { //这里通过map的大小来比较元素个数
DLinkedNode first = list.removeFirst();
map.remove(first.key);
}
list.addNode(node);
map.put(key,node);
}
}
}
get方法还可以这样写:
public int get(int key) {
DLinkedNode node = map.get(key);
if(node == null){
return -1; // should raise exception here.
}
// move the accessed node to the Tail;
this.moveToTail(node);
return node.value;
}
Java
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode(int key, int value) {
this.key = key;
this.value = value;
}
}
class DoubleList {
private DLinkedNode head, tail; // 头尾虚节点
public DoubleList() {//构造函数来初始化有两个节点的循环链表
head = new DLinkedNode(0, 0);
tail = new DLinkedNode(0, 0);
head.next = tail;
tail.prev = head;
head.prev = null;
tail.next = null;
}
//添加节点到末尾,先确定Node的前后,然后修改前后节点的指针
public void addNode(DLinkedNode node) {
node.prev = tail.prev;
node.next = tail;
tail.prev.next = node;
tail.prev = node;
}
//删除链表中的节点
public void removeNode(DLinkedNode node){
node.next.prev = node.prev;
node.prev.next = node.next;
}
//将某个确定的元素移到链表尾部
public void moveToTail(DLinkedNode node){
removeNode(node);
addNode(node);
}
//删除链表第一个节点并返回该节点
public DLinkedNode removeFirst(){
DLinkedNode res = head.next;
removeNode(res);
return res;
}
}
class LRUCache {
private int capacity = 0;
private HashMap<Integer, DLinkedNode> map = new HashMap<>();
private DoubleList list = new DoubleList();
//构造函数
public LRUCache(int capacity) {
this.capacity = capacity;
}
//get方法:返回元素值并把这个元素移动到链表尾部。
public int get(int key) {
if (map.containsKey(key)) {
DLinkedNode node = map.get(key);
list.moveToTail(node);
return node.value;
} else {
return -1;
}
}
//put方法
//(一)如果key已经存在,则修改对应key的value,将节点移动到末尾。
//(二)key不存在:新建节点,如果capacity满,则删除表头的节点,并将map中的key删除。将新节点插入到尾部,并在map中新建映射。
public void put(int key, int value) {
if (map.containsKey(key)) {
DLinkedNode node = map.get(key);
node.value = value;
list.moveToTail(node);
} else {
DLinkedNode node = new DLinkedNode(key,value);
if (capacity == map.size()) { //这里通过map的大小来比较元素个数
DLinkedNode first = list.removeFirst();
map.remove(first.key);
}
list.addNode(node);
map.put(key,node);
}
}
}
Python
Dict + Double LinkedList
#220ms
class Node:
def __init__(self, k, v):
self.key = k
self.val = v
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.dic = dict()
self.head = Node(0, 0)
self.tail = Node(0, 0)
self.head.next = self.tail
self.tail.prev = self.head
def get(self, key):
if key in self.dic:
n = self.dic[key]
self._remove(n)
self._add(n)
return n.val
return -1
def put(self, key, value):
if key in self.dic:
self._remove(self.dic[key])
n = Node(key, value)
self._add(n)
self.dic[key] = n
if len(self.dic) > self.capacity:
n = self.head.next
self._remove(n)
del self.dic[n.key]
def _remove(self, node):
p = node.prev
n = node.next
p.next = n
n.prev = p
def _add(self, node):
p = self.tail.prev
p.next = node
self.tail.prev = node
node.prev = p
node.next = self.tail
使用OrderedDict
#444ms
class LRUCache(object):
def __init__(self, capacity):
self.dic = collections.OrderedDict()
self.remain = capacity
def get(self, key):
if key not in self.dic:
return -1
v = self.dic.pop(key)
self.dic[key] = v # set key as the newest one
return v
def put(self, key, value):
if key in self.dic:
self.dic.pop(key)
else:
if self.remain > 0:
self.remain -= 1
else: # self.dic is full
self.dic.popitem(last=False)
self.dic[key] = value
使用字典和队列
#752ms
class LRUCache(object):
def __init__(self, capacity):
self.deque = collections.deque([])
self.dic = {}
self.capacity = capacity
def get(self, key):
if key not in self.dic:
return -1
self.deque.remove(key)
self.deque.append(key)
return self.dic[key]
def put(self, key, value):
if key in self.dic:
self.deque.remove(key)
elif len(self.dic) == self.capacity:
v = self.deque.popleft() # remove the Least Recently Used element
self.dic.pop(v)
self.deque.append(key)
self.dic[key] = value