146. LRU 缓存 - 力扣(LeetCode)
请你设计并实现一个满足 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)
的平均时间复杂度运行。
用一张图来理解整个流程
假设内存只能容纳3个页面,按照 7 0 1 2 0 3 0 4 的次序访问页面。假设内存按照栈的方式来描述访问时间:上面是最近访问的,下面是最远时间访问的,那LRU就是这样工作的:
HashMap + 双向链表
class LRUCache
{/*** 双向链表的结点*/class Node{int key; //结点的标识int value; //结点的值Node pre;Node next;public Node(){}public Node(int key, int value){this.key = key;this.value = value;}}Node dummyHead; //虚拟头结点Node dummyTail; //虚拟尾结点/*** 用于定位结点的HashMap*/HashMap<Integer, Node> hashMap = new HashMap<>();int capacity; //容量大小int size; //当前缓存大小public LRUCache(int capacity){this.capacity = capacity;this.size = 0;dummyHead = new Node();dummyTail = new Node();dummyHead.next = dummyTail;dummyTail.pre = dummyHead;}/*** 移除结点*/public void remove(Node node){//node前驱的后继改为node的后继node.pre.next = node.next;//node后继的前驱改为node的前驱node.next.pre = node.pre;}/*** 将独立的结点添加到链表头部*/public void addToHead(Node node){//头插法node.pre = dummyHead;node.next = dummyHead.next;dummyHead.next.pre = node; //原先表首结点的前驱改为nodedummyHead.next = node;}/*** 将双向链表中的结点移动到表首*/public void moveToHead(Node node){remove(node);addToHead(node);}public int get(int key){Node node = hashMap.get(key);//如果链表没有这个结点,直接返回-1if (node == null)return -1;//存在的话,先将它提到表首,再返回结点的值moveToHead(node);return node.value;}public void put(int key, int value){Node node = hashMap.get(key);//如果链表没有这个结点,则生成一个新结点,并放在表首if(node == null){Node newNode = new Node(key, value);addToHead(newNode);hashMap.put(key, newNode); //在哈希表中记录这个新结点//只有添加新结点的情况下才会时缓存大小变化size++;//若put之后超出容量,则在链表中删除尾结点,并在哈希表中除名if (size > capacity){Node tail = dummyTail.pre;remove(tail);hashMap.remove(tail.key);}}else //如果已经存在,则将node提到表首,并更新value值{moveToHead(node);node.value = value;}}
}