思路:
首先我们用一个双向链表来作为缓存区。这个双向链表list需要有一个头指针head,一个尾指针last,和一个记录当前长度的变量length。
这个双向链表的结点Node有一个指向上一结点的指针last, 还有一个指向下一结点的指针next,还有一个保存当前节点键的变量key,一个保存当前节点键值的变量val。
还有,我们要建立一个hashmap来保存当前缓存区中保存的节点的信息。<Integer, Node> 分别对应着每个节点的键和该节点的引用。
我们来分析一下,LRU的具体操作该如何用这两个数据结构来实现。
首先,当我们往缓存中put一个关键字<key,value>:
-
我们首先在hashmap中通过key来找这个节点是否存在。
-
如果存在,则修改hashmap中的值,并将该节点移动到list的头部。
-
如果不存在,则新建一个节点。我们还要判断一下,当前list是否已满,如果已满,我们就删除list尾端的节点,并且还要将尾部节点从hashmap中删除掉。然后将新节点插入到list的头部,并且将它加入hashmap。
然后我们来看get操作:
- 我们首先在hashmap中查看该节点是否存在。
- 如果不存在,那么直接返回-1。
- 如果存在,那么我们就将该节点移动到list的头部,并且返回该节点的val。
分析完了上面的两个操作,我们发现最核心的操作其实只有三个:一个是将某个节点直接插入list的头部,一个是将某个节点从list中删除,一个是将某个节点移动到list的头部。而且最后一个操作可以分解为,先将这个节点从list中删除,再将这个节点插入到list的头部。
那么,我们的双向链表的主要操作只有两个,一个是将某个节点插入到list的头部,一个是将某个节点从list中删除。
这里要注意一点,双向链表中,头指针和尾指针需要一直指向一个伪头节点和一个伪尾节点。这两个节点不保存任何信息。这么做的目的是始终不改变head和last的值,让插入和删除操作始终都在头结点和尾节点的中间进行,非常巧妙的减少了很多判断操作。
class LRUCache {
private class Node
{
Node last;
Node next;
int key;
int val;
public Node(int key,int val)
{
this.key = key;
this.val = val;
this.last = null;
this.next = null;
}
}
private class TowWayLL
{
Node head;
Node last;
int length;
public TowWayLL()
{
this.head = new Node(0,0);//添加伪头部结点
this.last = new Node(0,0);//添加伪尾部结点
head.next = last;
last.last = head;
this.length = 0;
}
public Node remove(Node n)
{
this.length--;
n.next.last = n.last;
n.last.next = n.next;
return n;
}
public void putTohead(Node n)//将该节点加入到头部
{
n.next = head.next;
n.last = head;
head.next.last = n;
head.next = n;
this.length++;
}
public void moveTohead(Node n)//将该节点移动到头部
{
this.remove(n);
this.putTohead(n);
}
}
HashMap<Integer,Node> map;;
TowWayLL list ;
int capacity;
public LRUCache(int capacity) {
map = new HashMap<>();
list = new TowWayLL();
this.capacity = capacity;
// System.out.println("capacity "+this.capacity);
}
public int get(int key) {
// System.out.println("get "+key);
Node n = map.getOrDefault(key,null);
if(n==null)
return -1;
else
{
list.moveTohead(n);
return n.val;
}
}
public void put(int key, int value) {
// System.out.println("put "+key+" "+value+" length "+ list.length + "capacity"+capacity);
Node n = map.getOrDefault(key,null);
if(n!=null)//如果结点已经存在,则先修改结点的value,再将该节点移动到list头部
{
n.val = value;
list.moveTohead(n);
}
else//如果结点不存在
{
n = new Node(key,value);//先新建结点
if(list.length==capacity)//如果当前缓存已满,则删除缓存队列中的最后一个,并将map中对应结点删除
{
Node last = list.remove(list.last.last);
map.remove(last.key);
}
list.putTohead(n);//将新节点插入头部
}
map.put(key,n);//最后将该节点重新放入map中
}
}
/**
* 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);
*/