请你设计并实现一个满足LRU (最近最少使用) 缓存约束的数据结构。
实现LRUCache类:
LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以O(1)的平均时间复杂度运行。
解决方案
定制双向链表
class LRUCache {
// 双向链表的节点
class Node {
// 属性:值、前驱和后继(实际上不应该用public,算法实现中为了方便才这样写)
int key;
int value;
public Node prev;
public Node next;
public Node() {}
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
private int capacity;
// 双向链表的逻辑头结点和逻辑尾节点
private Node head, tail;
Map<Integer, Node> keyNodeMap;
public LRUCache(int capacity) {
// 给虚拟头结点和虚拟尾节点赋值是为了debug的时候查看(因为规定了key和value都大于0)
head = new Node(-1,-1);
tail = new Node(-2,-2);
head.next = tail;
tail.prev = head;
this.capacity = capacity;
keyNodeMap = new HashMap<>();
}
/**
* 注意:整个函数的时间复杂度要求O(1)
*/
public int get(int key) {
int res = -1;
// 在返回前需要修改双向链表(并且要get的node不是最头部的节点)
if(keyNodeMap.containsKey(key)) {
Node cur = keyNodeMap.get(key);
res = cur.value;
moveNodeToHead(cur);
}
return res;
}
/**
* 注意:整个函数的时间复杂度要求O(1)
*/
public void put(int key, int value) {
// 先把新的值加进去,再判断新的size是否超过了capacity
Node node = keyNodeMap.get(key);
if(node == null) {
// 如果原来没有该key就new一个node加进去
node = new Node(key, value);
// 这个node需要加在链表的最前面
addNodeToHead(node);
if(keyNodeMap.size() > capacity) {
removeNode(tail.prev);
}
} else {
// 如果原来就有该key只需要修改值并将对应的node移到最前面
node.value = value;
moveNodeToHead(node);
}
}
private void removeNode(Node node) {
keyNodeMap.remove(node.key);
// 将这个节点的前驱和后继直接连起来
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void addNodeToHead(Node node) {
keyNodeMap.put(node.key, node);
// 然后把这个节点挪到双向链表的前面去
node.next = head.next;
node.prev = head;
head.next.prev = node;
head.next = node;
}
private void moveNodeToHead(Node node) {
removeNode(node);
addNodeToHead(node);
}
}