题解
-
设计和实现一个 LRU (最近最少使用) 缓存机制,(如果不懂LRU机制的,需要先学习操作系统的LRU调度算法,先从理论上知道实现方式才行),支持以下操作: 获取数据 get 和 写入数据 put ,且两个操作都需要在
O(1)
复杂度下完成 -
LRU算法大意:给定一个固定大小的容器,容器中的事物代表需要使用的事物,以及一系列事物,每个事物都有自己的
key, value
值,其中key用来识别自身的身份,value则是内容。在容器没有满的时候,事物会不断向容器里面添加,容器满的时候,就把最早添加到容器中的事物删除,在添加事物的时候,如果遇到容器中已经有了的事物,就不往容器里面添加,而是直接把之前放入容器的事物调到容器的尾部,表示它为最近使用事物
,从而实现淘汰掉的事物是最近最少使用
的事物。 -
题解:因为涉及到的添加与淘汰操作就是在头部和尾部执行的,我们可以直接考虑使用链表,每当加入一个数据,就往尾部添加,删除一个数据,就把头部删除,因为使用了链表,我们后面就将事物成为节点。
- 考点1:但是
get
方法是查找LRU中的节点,我们知道,对于普通链表来说,查找的复杂度是o(n)
,但是题目要求使用o(1)
的复杂度,因此我们不能直接使用普通的链表,而o(1)
复杂度只有哈希能做到,因此我们使用一个额外空间来存储一个哈希映射Map<key, node>
,这样,在get方法中,就可以通过哈希直接得到节点。 - 考点2:既然叫做LRU算法, 那么调用
get
方法访问一个节点的时候,该节点就是最近访问的节点了,因此对应的位置移动到尾部,此外,put
方法在放入具有相同key
的数据的时候,也要移动节点的位置。- 对于链表来说,移动到尾部其实就是删除对应位置的节点,然后再在尾部添加节点,那么这个操作要如何实现
o(1)
的复杂度呢 - 首先,链表必须是双向链表,因为删除节点的操作就是跨越当前节点建立前后节点的链接,从而
孤立
当前节点,`cur.next.prev = cur.prev; cur.prev.next=cur.next,如果不是双向链表,那么获取前一个节点的操作是非常耗时的。 - 然后维护一个超级头部
head
和超级尾部tail
,tail.prev
代表最近使用的节点,head.next
代表可能即将被删除的节点,那么插入到尾部就是tail.prev.next = cur; cur.prev = tail.prev; cur.next = tail;
- 对于链表来说,移动到尾部其实就是删除对应位置的节点,然后再在尾部添加节点,那么这个操作要如何实现
- 考点1:但是
-
实现:注意不要忘记长度的变化,以及节点位置变化之后对
map
的更新
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {}
public DLinkedNode(int _key, int _value) {key = _key; value = _value;}
}
class LRUCache {
private Map<Integer, DLinkedNode> m;
private DLinkedNode head;
private DLinkedNode tail;
private int capacity;
private int len;
public LRUCache(int capacity) {
this.capacity = capacity;
len = 0;
m = new HashMap<>();
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode cur = m.get(key);
if(cur == null) return -1;
DLinkedNode cur_prev = cur.prev;
DLinkedNode cur_next = cur.next;
cur_prev.next = cur_next;
cur_next.prev = cur_prev;
addToTail(cur);
m.replace(key, cur);
return cur.value;
}
public void addToTail(DLinkedNode cur){
//建立与倒数第二个节点的链接
tail.prev.next = cur;
cur.prev = tail.prev;
//建立与尾巴的链接
cur.next = tail;
tail.prev = cur;
}
public void put(int key, int value) {
if(get(key) ==-1) {
len++;
DLinkedNode tmp = new DLinkedNode(key, value);
addToTail(tmp);
m.put(key, tmp);
if(len > capacity){
m.remove(head.next.key);
head = head.next;
head.prev = null;
len--;
}
}
else{
m.get(key).value = value;
m.replace(key, m.get(key));
}
}
}
- 此题的启发:想要让查询时间编程
o(1)
,无论什么数据结构(链表,树等等)都可以额外附加一个哈希映射,利用空间复杂度来换时间