146J. LRU Cache
https://leetcode.com/problems/lru-cache/
LRU Cache 是一道很高频的题目,非常重要。
它本身的意义就是为了学习 LinkedHashMap/OrderedDict
这个数据结构。而这个数据结构的存在最典型的应用就是LRU Cache,
所以真的是非常重要。
LRU的意思是Least Recently Used Cache.
相关的题目还有 460 LFU Cache Least Frequently Used Cache
这是介绍各种Cache的Wikipedia。
https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU
可以暂时不太学习 LinkedHashMap在Java中是怎么实现的,但是此题
的解决方案实际上是差不多的。
以下是两个中文讲解LinkedHashMap的文章
https://juejin.im/post/5a4b433b6fb9a0451705916f
https://blog.csdn.net/justloveyou_/article/details/71713781
Method 1 Double LinkedList + HashMap
如果不考虑时间复杂度O(1)的要求,仅仅实现LRU,
思路是这样的,先创建一个DoubleLinkedList,因此我们需要
定义DoubleLinkedList,和传统的不同的是,我们需要给每个Node两个值,
一个Key, 一个Val. 定不定义Constructor不重要。
我们看这个双向链表是怎么实现LRU的呢?
我们先建立一个dummyHead和一个dummyTail。由于是双向链表,所以不可能只
创建一个dummyHead。如果新加入了节点,就加在头部之后。那么最靠近尾部的
就是Least Recent Used的Node。
因此我们首先需要写一个函数addNode,用来在dummyHead之后加节点。
然后如果一个已经存在的节点重新被使用了怎么办呢?我们应该找到这个节点
并把它移动到头部之后。因此我们需要写两个空返回类型函数,一个是removeNode,
用来删掉该节点。一个是moveToHead来实现这个过程,其实现方式是,先
用removeHead删掉它,再用addNode把它加到dummyHead之后。
所以针对双向链表我们需要实现三个空返回类型的函数:
addNode, removeNode, moveToHead. 这三个都是对链表做一些修改的函数。
如果我们加入了新的本来不存在的值,导致我们的双向链表现在有了N+1个Node,
这就意味着我们的Cache需要删掉一些东西,即删掉LRU。
那么我们就直接删掉最靠近尾部的Node即可. 需要一个函数popTail.
为什么是PopTail并返回该节点?
因为为了实现O(1)的时间复杂度从链表删去节点的同时,还要照顾我们的HashMap。
为了实现O(1),自然而然的会想到使用HashMap。我们用HashMap的Key直接取到
双向链表的对应的Node,自然可以从Node中读取Val。这就可以实现get的O(1).
当然,get这件事本身就相当于用了一次对应Node,还要用moveToHead
把这个Node给提到最前面去。
那么put如何O(1)呢?其实put本来就是O(1)的,这是链表实现保证的。只需要处理
一下添加了Node之后的一些情况。这部分是最为平庸的。
因此我们的LRU除了双向链表之外,
类变量还需要有size,capacity,链表头尾,和一个HashMap。
以下的代码基本上就是上面叙述的部分了。
class LRUCache {
// Doublely Linked List
// Define Doubly Linked List
class DLListNode{
public int key; // for LRU
// Standard
public int val;
public DLListNode next;
public DLListNode prev;
}
public void addNode(DLListNode node){
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
public void remove(DLListNode node){
node.next.prev = node.prev;
node.prev.next = node.next;
// 为什么不必要以下两句呢?因为没必要让node被回收,
// remove只在moveToHead中使用,一会儿还要addnode呢。
// 当然加上以下两句也不会报错
// node.next = null;
// node.prev = null;
}
public void moveToHead(DLListNode node){
remove(node);
addNode(node);
}
//为何popTail要返回节点?因为还要从HashMap里删掉该节点的信息
public DLListNode popTail(){
DLListNode poped = tail.prev;
remove(poped);
return poped;
}
// For LRU Cache
private int size;
private int capacity;
private DLListNode head;
private DLListNode tail;
private Map<Integer, DLListNode> cache;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
head = new DLListNode();
tail = new DLListNode();
head.next = tail;
tail.prev = head;
cache = new HashMap<Integer, DLListNode>();
}
public int get(int key) {
DLListNode node = cache.get(key);
if(node == null) return -1;
moveToHead(node);
return node.val;
}
public void put(int key, int value) {
DLListNode node = cache.get(key);
if(node!=null){
node.val = value;
moveToHead(node);
} else{
node = new DLListNode();
node.key = key;
node.val = value;
addNode(node);
size++;
cache.put(key,node);
if(size > capacity){
DLListNode poped = popTail();
cache.remove(poped.key);
size--;
}
}
}
}
以下是答案
public class LRUCache {
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
}
private void addNode(DLinkedNode node) {
/**
* Always add the new node right after head.
*/
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(DLinkedNode node){
/**
* Remove an existing node from the linked list.
*/
DLinkedNode prev = node.prev;
DLinkedNode next = node.next;
prev.next = next;
next.prev = prev;
}
private void moveToHead(DLinkedNode node){
/**
* Move certain node in between to the head.
*/
removeNode(node);
addNode(node);
}
private DLinkedNode popTail() {
/**
* Pop the current tail.
*/
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
private Map<Integer, DLinkedNode> cache = new HashMap<>();
private int size;
private int capacity;
private DLinkedNode head, tail;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
head = new DLinkedNode();
// head.prev = null;
tail = new DLinkedNode();
// tail.next = null;
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) return -1;
// move the accessed node to the head;
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if(node == null) {
DLinkedNode newNode = new DLinkedNode();
newNode.key = key;
newNode.value = value;
cache.put(key, newNode);
addNode(newNode);
++size;
if(size > capacity) {
// pop the tail
DLinkedNode tail = popTail();
cache.remove(tail.key);
--size;
}
} else {
// update the value.
node.value = value;
moveToHead(node);
}
}
}
这是我最新写的,感觉不错的。比上面的标准答案好多了。 对于 DLList 我们只实际上实现 addToHead 和 remove 两个方法就可以。
class LRUCache {
class Node {
int key;
int val;
Node prev;
Node next;
public Node() {};
public Node(int key, int val) {
this.key = key;
this.val = val;
}
}
class DLList{
Node head;
Node tail;
int size;
public DLList(){
this.head = new Node(-1, -1);
this.tail = new Node(-1, -1);
this.head.next = this.tail;
this.tail.prev = this.head;
this.size = 0;
};
public Node remove(Node node) {
Node prev = node.prev;
Node next = node.next;
prev.next = next;
next.prev = prev;
this.size -= 1;
return node;
}
public void add(Node node) {
Node next = head.next;
node.prev = head;
node.next = next;
head.next = node;
next.prev = node;
this.size += 1;
}
public Node getLast() {
return this.tail.prev;
}
}
private int capacity;
private Map<Integer, Node> map;
private DLList list;
public LRUCache(int capacity) {
this.capacity = capacity;
this.map = new HashMap<>();
this.list = new DLList();
}
public int get(int key) {
if( !map.containsKey(key) ) {
return -1;
}
Node node = map.get(key);
update(node);
return node.val;
}
public void put(int key, int value) {
if( map.containsKey(key) ) {
map.get(key).val = value;
update(map.get(key));
return;
}
Node node = new Node(key, value);
map.put(key, node);
list.add(node);
while(map.size() > capacity) {
Node last = list.remove( list.getLast() );
map.remove( last.key );
}
}
public void update(Node node) {
list.add( list.remove(node) );
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/
Last updated
Was this helpful?