题目:LRU缓存机制 - LeetCode (中国)
其实现原理可以用一张图来表示:k-v储存在map中,用一个双向链表来维持访问顺序。
为什么要用双向链表?因为可以在O(1)内删除某个节点。
等等,似乎单向链表也可以快速删除,比如《编程之美》有一道题目是从无头的单向链表中快速删除一个节点。为什么在这里为什么不能使用呢?
1)这不是真正的删除,而是替换。真正删除的是下一个节点。
2)这种方法无法“删除”尾节点。
class LRUCache {
final int capacity;
Node head;
Node tail;
Map cache = new HashMap<>();
static class Node {
public Node(int key, int value) {
this.key = key;
this.value = value;
}
int key;
int value;
Node pre;
Node next;
}
public LRUCache(int capacity) {
this.capacity = capacity;
}
public int get(int key) {
Node node = cache.get(key);
if (node == null) {
return -1;
}
remove(node);
setHead(node);
return node.value;
}
private void setHead(Node node) {
node.pre = null; // 注释 1
node.next = null;
if (head == null) {
head = tail = node;
} else {
node.next = head;
head.pre = node;
head = node;
}
}
private void remove(Node node) {
Node before = node.pre;
Node after = node.next;
if (before == null) {
head = after;
} else {
before.next = after;
}
if (after == null) {
tail = before;
} else {
after.pre = before;
}
}
public void put(int key, int value) {
Node node = cache.get(key);
if (node != null) {
remove(node);
node.value = value; // 注释3
setHead(node);
} else {
Node newNode = new Node(key, value);
if (cache.size() >= capacity) {
cache.remove(tail.key); // 注释 2
remove(tail);
}
setHead(newNode);
cache.put(key, newNode);
}
}
}
我以前没有写过双向链表的代码,所以有兴趣写了写,发现了一些bug。
注释1:从链表中删除元素,除了要保证原链表不能断开之外,还需要考虑到被删除节点的指针往往非空,所以这里需要人为置空。
注释2:当cache处于full状态时,需要删除同时从链表和map中删除改元素,很容易忽略map。而且注释2和下一行的顺序不能交换,因为先执行remove(tail),tail的指针就变化了。
注释3:这里不用生成一个新节点,用newValue替换原值即可。
这道题目还有更方便的实现……本来想引用自己过去的一篇文章,但是知乎的产品设计失去了以前检索文章的功能,只能罢了。
参考:LeetCode - LRU Cache (Java)