题目
Design and implement a data structure for Least Recently Used (LRU) 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 reached its capacity, it should invalidate the least recently used item before inserting a new item.
LRU Cache
LRU是Least Recent Used的缩写,即最少使用的Cache置换算法。是为虚拟页式存储管理服务的。Cache是快速缓存。这是IT行业常常见到的概念。CPU中的Cache能极大提高存取指令和数据的时间,让整个存储器(Cache+内存)既有Cache的快速度,又有内存的大容量。
Cache尽管速度快,可是容量有限。因此当Cache容量用完而又有新的内容加入进来的时候,就须要选取Cache中的部分内容进行舍弃,然后加入新的内容。LRU Cache的替换规则是:每次选取最久不被使用的内容进行舍弃,然后加入新的内容。之前上操作系统,老师告诉我的LRU Cache Algorithm中文翻译是近期最久未使用算法。
思路
LRU的典型实现是double linked list + hash map。
原理是:
- 双向链表根据每一个节点近期被訪问的时间有序存储,近期被訪问的节点存储在表头,近期没有被訪问的节点存储的表尾,存储根据是由于:近期被訪问的节点在接下来的一段时间仍有非常大的概率被再次訪问到。
- 哈希表的作用是用来提高查找效率,假设不使用哈希表。则查找一个节点的时间复杂度是O(n)。而使用了哈希表,则每一个节点的查找时间复杂度为O(1)。
查找(GET)操作:
- 依据键值查找hashmap,假设没找到直接返回-1
- 若找到相应节点node,则将其插入到双向链表表头
- 返回node的value值
插入(SET)操作:
- 依据键值查找hashmap。假设找到。则直接将该节点移到表头就可以
- 假设没有找到。首先推断当前Cache是否已满
- 假设已满,则删除表尾节点
- 将新节点插入到表头
AC代码
AC代码例如以下,写的比較乱,事实上非常多方法是能够简洁复用的,可是这里之所以不改,是为了让大家更好的清楚LRU的流程和原理:
import java.util.HashMap; public class LRUCache {
private HashMap<Integer, DoubleListNode> mHashMap;
private DoubleListNode head;
private DoubleListNode tail;
private int capacity;
private int currentsize; public LRUCache(int capacity) {
this.capacity = capacity;
this.currentsize = 0;
this.mHashMap = new HashMap<Integer, DoubleListNode>();
this.head = this.tail = null;
} public int get(int key) {
if (mHashMap.containsKey(key)) {
DoubleListNode tNode = mHashMap.get(key);
if (tNode == tail) {
if (currentsize > 1) {
removeNodeFromTail();
moveNodeToHead(tNode);
}
} else if (tNode == head) {
// do nothing
} else {
tNode.pre.next = tNode.next;
tNode.next.pre = tNode.pre;
moveNodeToHead(tNode);
}
return mHashMap.get(key).value;
} else {
return -1;
}
} private void removeNodeFromTail() {
tail = tail.pre;
if (tail != null) {
tail.next = null;
}
} private void moveNodeToHead(DoubleListNode node) {
head.pre = node;
node.next = head;
node.pre = null;
head = node;
} public void set(int key, int value) {
if (mHashMap.containsKey(key)) {
// 更新HashMap中相应的值,并将key相应的Node移至队头
DoubleListNode tNode = mHashMap.get(key);
tNode.value = value;
if (tNode == tail) {
if (currentsize > 1) {
removeNodeFromTail();
moveNodeToHead(tNode);
}
} else if (tNode == head) {
// do nothing
} else {
tNode.pre.next = tNode.next;
tNode.next.pre = tNode.pre;
moveNodeToHead(tNode);
} mHashMap.put(key, tNode);
} else {
DoubleListNode node = new DoubleListNode(key, value);
mHashMap.put(key, node);
if (currentsize == 0) {
head = tail = node;
currentsize += 1;
} else if (currentsize < capacity) {
moveNodeToHead(node);
currentsize += 1;
} else {
// 删除tail节点。而且添加一个head节点
mHashMap.remove(tail.key);
removeNodeFromTail(); // 添加头节点
moveNodeToHead(node);
}
}
} public static void main(String[] args) {
LRUCache lruCache = new LRUCache(1);
lruCache.set(2, 1);
System.out.println(lruCache.get(2));
lruCache.set(3, 2);
System.out.println(lruCache.get(2));
System.out.println(lruCache.get(3));
} private static class DoubleListNode {
public DoubleListNode pre;
public DoubleListNode next;
public int key;
public int value; public DoubleListNode(int key, int value) {
this.key = key;
this.value = value;
this.pre = this.next = null;
}
} }