题意:本题的题目较难读懂,不过这能考察一个面试者能否短时间内,理解一段未见过的内容的能力。在题目的wiki链接里提供了这样一张图:
缓存的长度(capacity )为4,A、B、C、D首先插入缓存中;插入E时,超过了capacity ,需要覆盖最近最少使用的A;插入F时,超过了capacity ,需要覆盖B。
为最近最少使用(LRU)的缓存策略设计一个数据结构,它应该支持以下操作:获取数据(get)和写入数据(put)。
1.获取数据get(key):如果缓存中存在key,则获取其数据值(通常是正数),否则返回-1。
2.写入数据put(key, value):如果key还没有在缓存中,则写入其数据值。当缓存达到上限,它应该在写入新数据之前删除最近最少使用的数据用来腾出空闲位置。
要求:时间复杂度为O(1)。
例子:
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(此时最近最少使用是2,因为1被get操作过)
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
分析:
要求时间复杂度为O(1),因此很容易联想到哈希表,同时题目中没有给定节点的结构,所以我们可以定义一个双向链表,这样方便我们查找。同时使用哈希表和双向链表才能保证常数的时间复杂度。
哈希表跟踪双链表中的键和值,即key为node的值,value为node。为了使代码更加整洁,需要创建了一个伪头和伪尾的标记边界,所以我们不需要在更新过程中检查NULL节点。
具体到get和put方法并不是很难,关键是把存取的过程想清楚就不难写出代码。
import java.util.Hashtable;
class LRUCache {
class DLinkedNode {
int key;
int value;
DLinkedNode pre;
DLinkedNode post;
}
/**
* Always add the new node right after head;
*/
private void addNode(DLinkedNode node){
node.pre = head;
node.post = head.post;
head.post.pre = node;
head.post = node;
}
/**
* Remove an existing node from the linked list.
*/
private void removeNode(DLinkedNode node){
DLinkedNode pre = node.pre;
DLinkedNode post = node.post;
pre.post = post;
post.pre = pre;
}
/**
* Move certain node in between to the head.
*/
private void moveToHead(DLinkedNode node){
this.removeNode(node);
this.addNode(node);
}
// pop the current tail.
private DLinkedNode popTail(){
DLinkedNode res = tail.pre;
this.removeNode(res);
return res;
}
private Hashtable<Integer, DLinkedNode>
cache = new Hashtable<Integer, DLinkedNode>();
private int count;
private int capacity;
private DLinkedNode head, tail;
public LRUCache(int capacity) {
this.count = 0;
this.capacity = capacity;
head = new DLinkedNode();
head.pre = null;
tail = new DLinkedNode();
tail.post = null;
head.post = tail;
tail.pre = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if(node == null){
return -1; // should raise exception here.
}
// move the accessed node to the head;
this.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;
this.cache.put(key, newNode);
this.addNode(newNode);
++count;
if(count > capacity){
// pop the tail
DLinkedNode tail = this.popTail();
this.cache.remove(tail.key);
--count;
}
}else{
// update the value.
node.value = value;
this.moveToHead(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);
*/