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.
这道题主要是实现“最近最少使用替换”算法。
Cache的大小是有限的,当增加新的值时,如果没有额外空间,就需要采用替换算法。替换算法的核心就是要维护一个状态,即:哪个值应该被替换出去,哪个值是最新的。
同时当调用get方法时,需要更新刚索引过的值,将其状态设置为最新,再更新其他值的状态,最近最少使用的那个值将更新为:下次被替换出去。set方法同样需要更新各个值的状态。
根据上述的分析,我们可以采用单向链表来实现该替换算法。该链表需要维护一个尾指针。我们将最新状态的值放置在尾部,最近最少使用的值放在头部。当没有额外空间时,加入新值,我们在尾部添加,同时删除头部的旧值。当更新值得状态时,对应的操作就是链表的删除和插入操作。
但是链表的缺点就是遍历耗时。如何在O(1)时间内访问到链表中的值呢?可以结合Hash表的性质。hash表内存储节点的key值和节点的指针。当调用get方法时,利用hashMap获取到包含该key的节点,时间为O(1)。此时,需要做的就是要维护hashmap和链表的一致性。
import java.util.HashMap;
public class LRUCache {
private LinkList list;
public LRUCache(int capacity) {
list = new LinkList(capacity);
}
public int get(int key) {
return list.get(key);
}
public void set(int key, int value) {
list.set(key, value);
}
public void print(){
list.printList();
}
class LinkList{
private int capacity;//链表总长度
private int size;//当前链表节点个数
private Node head;//头指针
private Node tail;//尾指针
private HashMap<Integer, Node> map;
public LinkList(int capacity){
this.capacity = capacity;
this.size = 0;
head = new Node();
tail = head;
tail.next = null;
map = new HashMap<Integer,Node>(capacity);
}
public int get(int key){
int val = -1;
if(map.containsKey(key)){
Node node = map.get(key);
val = node.value;
this.update(node);//更新链表和hashmap状态
return val;
}else{
return val;
}
}
public void set(int key, int value){
if(map.containsKey(key)){
Node node = map.get(key);
node.value = value;
this.update(node);
}else{
Node node = new Node(key, value);
node.next = null;
tail.next = node;
tail = node;
map.put(key, node);
if(size < capacity){
size++;
}else{
Node p = head.next;
int k = p.key;
head.next = p.next;
p.next = null;
p = null;
map.remove(k);
}
}
}
//当调用get和set方法时,当前节点应该需要置于最新状态,即将该节点放置到链表的尾部。采用的是单向链表,节点的删除需要从头遍历链表到当前节点的前一个节点,时间
//时间复杂度为O(n),这里采用O(1)的时间复杂度删除节点p。
//交换节点p和p.next的值,此时删除p.next,即将该节点插入到尾部即可。但需要注意的是在hashmap中就要将p.key和p.next.key的节点交换一下,否则hashmap维护的节点
//就是错误的了。
public void update(Node p){
if(p != tail){
Node p2 = p.next;
map.put(p.key, p2);//更新hashmap中的节点
map.put(p2.key, p);//更新hashmap中的节点
int temp = p.key;
p.key = p2.key;
p2.key = temp;
temp = p.value;
p.value = p2.value;
p2.value = temp;
if(p2 != tail){
p.next = p2.next;
p2.next = null;
tail.next = p2;
tail = p2;
}
}
}
public void printList(){
Node p = head.next;
while(p != null){
System.out.print(p.key +"->" + p.value + ",");
p = p.next;
}
System.out.println();
}
}
class Node{
int key;
int value;
Node next;
public Node(){
}
public Node(int key, int value){
this.key = key;
this.value = value;
}
}