题目分析
有get和put,这是一个键值操作,第一反应是使用map,但是题目有限定条件:首先键值对的大小是有限的,当put键值对的数量超过最大长度时,需要舍弃一些键值对,即那些不常用的键值对。
何为不常用的键值对?
题目给出的描述为最近最少使用的键值对是不常用键值对,也就是说最近进行put,get操作的键值最不会被淘汰,这样就产生了一个类似队列或者链表的模型:
1)每次进行put操作时,如果该值存在于队列之中,就将其拿出并压入队首,如果不存在,则淘汰队尾元素,将该元素插入队头
2)每次进行get操作时,如果该值存在于队列之中,将其放入队首,并返回value,反则返回-1
显然,实现2)中的操作后1)中的部分操作可由2)完成。
实现思路
方法一:
题目要求,所有操作在O(1)的时间完成,只能使用HashMap,而按照题目分析,我们应该有一个类似链表的模型来管理HashMap,这里就可以使用将两者结合,使用HashMap通过key来查询每个单元的地址,双向链表模型来管理每个单元的位置,这样就可以实现全方位O(1)了。
单元应当定义为
class Node{
int key;
int value;
Node pre;
Node next;
Node(int key,int value){
this.key = key;
this.value = value;
pre = null;
next = null;
}
}
方法二:
直接使用LinkedHashMap
代码和注释
class Node{
int key;
int value;
Node pre;
Node next;
static int length = 0;
Node(int key,int value){
this.key = key;
this.value = value;
this.pre = null;
this.next = null;
}
}
class LRUCache {
Map<Integer, Node> map = new HashMap<>();
private int capacity;
private Node dummyNode = new Node(-1,0);
private Node head = null;
private Node tail = null;
public LRUCache(int capacity) {
//这个很关键
Node.length = 0;
this.capacity = capacity;
}
public int get(int key) {
if(map.containsKey(key)){
Node node = map.get(key);
//如果该点是尾结点,不需要做任何处理
if(node.key == tail.key){
return tail.value;
}
//否则进行链表操作,将该点挪到尾部
node.pre.next = node.next;
node.next.pre = node.pre;
tail.next = node;
node.pre = tail;
tail = node;
//head可能也更改了
head = dummyNode.next;
// if(head!=null&&key == 1){
// System.out.println("head "+head.key+" tail "+tail.key);
// }
return tail.value;
}
return -1;
}
public void put(int key, int value) {
//如果链表中存在key,get操作已经将该值挪到尾部
if(this.get(key)!=-1){
tail.value = value;
return;
}
//将其加入尾之后
Node cur = new Node(key,value);
//如果链表中不存在,压入HashMap中
map.put(key,cur);
//先判断是否达到容量上限
//达到上限,去头,并在map中将其移除,加尾
if(cur.length==capacity){
map.remove(head.key);
dummyNode.next = head.next;
if(head.next!=null){
//去头
head.next.pre = dummyNode;
head = head.next;
//加尾
tail.next = cur;
cur.pre = tail;
tail = tail.next;
}
else{
dummyNode.next = cur;
cur.pre = dummyNode;
head = cur;
tail = cur;
}
head = dummyNode.next;
return;
}
//如果尾部为空,表示插入第一个元素
if(tail==null){
dummyNode.next = cur;
cur.pre = dummyNode;
head=cur;
tail=cur;
}
//否则,就接上去
else{
tail.next = cur;
cur.pre = tail;
tail = cur;
}
head = dummyNode.next;
//接进去了一个,自增
cur.length++;
}
}
/**
* 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);
*/
注意事项
1)因为链表的特殊性,如果定义了哑结点、头指针和尾指针,在链表发生结构变化的时候要顶住这几个点,考虑各种情况
2)由于Node中的length是静态变量,在每次LRU初始化的时候都要进行回零操作