题目描述:
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
实现 LRUCache 类:
- LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
- int get(int key):如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
- void put(int key, int value):如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
示例:
输入
[“LRUCache”, “put”, “put”, “get”, “put”, “get”, “put”, “get”, “get”, “get”]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]
解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4
分析:
通过分析题目描述,我们可以得出以下几点:
- 在调用get()方法的时候存储的是键值对,所以我们应该联想到
Map
- 在调用get()/set()方法时,被操作的数据都是最近刚使用的,所以应该把被操作的数据置为优先级最高,具体使用什么方式下面说。
对于以上两点,第一点很容易想到,但是第二点每次把数据操作之后都需要调整它的顺序,一开始我想到队列,队列是先进先出的特性,对尾插入元素,删除从队头删除,因此可以将队头元素看作优先级最低,对尾元素每次都是优先级最高。
但是,它还存在一个问题,就是当有元素重复了,我需要先将该元素从队列删除(此时可能不是队头元素),然后再进行对尾插入,可是队列遍历好像不是很容易实现,比较麻烦。
因此,对于优先级问题,我们采用链表
的形式:
- 头插:链表头元素优先级最高
- 删除:只需通过指针域就可以进行删除,O(1)
- 遍历:方便实现
举个例子:就以测试用例为例:[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]],容量为2
执行[1, 1], [2, 2],存储结构如下图所示:
因此,我们需要自己定义链表节点类:
public class Node{
public int key;
public int value;
public Node next=null;
public Node pre=null;
public Node(int key,int value)
{
this.key=key;
this.value=value;
}
}
Map数据结构:
Map<Integer,Node> map=new HashMap<>();
代码实现:
class LRUCache {
public int capacity;
public class Node{
public int key;
public int value;
public Node next=null;
public Node pre=null;
public Node(int key,int value)
{
this.key=key;
this.value=value;
}
}
Node head=new Node(-1,-1);
Node tail=new Node(-1,-1);
Map<Integer,Node> map=new HashMap<>();
public LRUCache(int capacity) {
head.next=tail;
tail.pre=head;
this.capacity=capacity;
}
public int get(int key) {
//如果该key不在Map中,直接返回-1
if(!map.containsKey(key))
{
return -1;
}else{//如果在map中,删除该节点,再将其头插到链表中
Node tmp=map.get(key);
//删除
tmp.pre.next=tmp.next;
tmp.next.pre=tmp.pre;
//头插
tmp.next=head.next;
tmp.pre=head;
head.next=tmp;
tmp.next.pre=tmp;
return tmp.value;
}
}
public void put(int key, int value) {
//map中已经存在:删除该节点,再头插
if(map.containsKey(key))
{
//删除
Node tmp=map.get(key);
tmp.next.pre=tmp.pre;
tmp.pre.next=tmp.next;
map.remove(key);
}else{//不存在:如果容量已满,将链表尾删除再头插
//map的元素个数大于等于容量,
if(map.size()>=capacity)
{
//删除
Node tmp=tail.pre;
tail.pre=tmp.pre;
tmp.pre.next=tail;
map.remove(tmp.key);
}
}
Node tmp=new Node(key,value);
map.put(key,tmp);
tmp.next=head.next;
head.next.pre=tmp;
head.next=tmp;
tmp.pre=head;
}
}