165 lines
5.5 KiB
Java
165 lines
5.5 KiB
Java
package 实现类功能;
|
||
/**
|
||
* 题目: 146. LRU 缓存 (LRUCache)
|
||
* 描述:请你设计并实现一个满足 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) 的平均时间复杂度运行。
|
||
|
||
示例 1:
|
||
输入
|
||
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
|
||
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
|
||
输出
|
||
[null, null, null, 1, null, -1, null, -1, 3, 4]
|
||
|
||
* 链接:https://leetcode.cn/problems/lru-cache/
|
||
*/
|
||
import java.util.HashMap;
|
||
import java.util.Map;
|
||
//二刷不会 学习
|
||
|
||
/**
|
||
* 对于 get 操作,首先判断 key 是否存在:
|
||
* 如果 key 不存在,则返回 −1;
|
||
* 如果 key 存在,则 key 对应的节点是最近被使用的节点。通过哈希表定位到该节点在双向链表中的位置,并将其移动到双向链表的头部,最后返回该节点的值。
|
||
*
|
||
* 对于 put 操作,首先判断 key 是否存在:
|
||
* 如果 key 不存在,使用 key 和 value 创建一个新的节点,在双向链表的头部添加该节点,并将 key 和该节点添加进哈希表中。然后判断双向链表的节点数是否超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希表中对应的项;
|
||
* 如果 key 存在,则与 get 操作类似,先通过哈希表定位,再将对应的节点的值更新为 value,并将该节点移到双向链表的头部。
|
||
*
|
||
*
|
||
* 注意,key和value不必相等
|
||
*/
|
||
public class LRUCache {
|
||
|
||
/* ----------------------------- 链表节点定义 ----------------------------- */
|
||
|
||
/**
|
||
* 双向链表节点
|
||
*/
|
||
class DLinkedNode {
|
||
int key; // 保存 key:便于淘汰节点时同步从 cache 中删除
|
||
int value; // 保存 value
|
||
DLinkedNode prev; // 前驱
|
||
DLinkedNode next; // 后继
|
||
|
||
public DLinkedNode() {}
|
||
|
||
public DLinkedNode(int _key, int _value) {
|
||
key = _key;
|
||
value = _value;
|
||
}
|
||
}
|
||
|
||
/* ----------------------------- 成员变量 ----------------------------- */
|
||
|
||
private final Map<Integer, DLinkedNode> cache = new HashMap<>(); // 哈希表
|
||
private int size; // 当前键值对数量
|
||
private final int capacity; // 最大容量
|
||
|
||
// 伪头、伪尾(固定不动)
|
||
private final DLinkedNode head;
|
||
private final DLinkedNode tail;
|
||
|
||
/* ----------------------------- 构造函数 ----------------------------- */
|
||
|
||
/**
|
||
* @param capacity 缓存最大容量 (正整数)
|
||
*/
|
||
public LRUCache(int capacity) {
|
||
this.size = 0;
|
||
this.capacity = capacity;
|
||
|
||
// 创建伪头、伪尾并相连
|
||
head = new DLinkedNode();
|
||
tail = new DLinkedNode();
|
||
head.next = tail;
|
||
tail.prev = head;
|
||
}
|
||
|
||
/* ----------------------------- 公共接口 ----------------------------- */
|
||
|
||
/**
|
||
* @param key 查询的键
|
||
* @return 若存在则返回对应 value,否则返回 -1
|
||
*/
|
||
public int get(int key) {
|
||
DLinkedNode node = cache.get(key);
|
||
if (node == null) { // 不存在
|
||
return -1;
|
||
}
|
||
// 存在:移动到链表头部(标记为最近使用)
|
||
moveToHead(node);
|
||
return node.value;
|
||
}
|
||
|
||
/**
|
||
* 写入或更新键值对
|
||
* @param key 键
|
||
* @param value 值
|
||
*/
|
||
public void put(int key, int value) {
|
||
DLinkedNode node = cache.get(key);
|
||
|
||
if (node == null) {
|
||
/* ---------- 新键 ---------- */
|
||
DLinkedNode newNode = new DLinkedNode(key, value);
|
||
cache.put(key, newNode); // 哈希表记录
|
||
addToHead(newNode); // 链表头插
|
||
++size;
|
||
|
||
// 超容量:弹出尾节点(最久未使用)
|
||
if (size > capacity) {
|
||
DLinkedNode tail = removeTail(); // 真尾节点
|
||
cache.remove(tail.key); // 同步删除哈希表项
|
||
--size;
|
||
}
|
||
} else {
|
||
/* ---------- 已存在键:更新值并提到头部 ---------- */
|
||
node.value = value;
|
||
moveToHead(node);
|
||
}
|
||
}
|
||
|
||
/* ----------------------------- 链表操作辅助 ----------------------------- */
|
||
|
||
/**
|
||
* 将节点插到伪头之后(链表头部)
|
||
*/
|
||
private void addToHead(DLinkedNode node) {
|
||
node.prev = head;
|
||
node.next = head.next;
|
||
head.next.prev = node;
|
||
head.next = node;
|
||
}
|
||
|
||
/**
|
||
* 从当前链表中删除节点(不删除哈希表项)
|
||
*/
|
||
private void removeNode(DLinkedNode node) {
|
||
node.prev.next = node.next;
|
||
node.next.prev = node.prev;
|
||
}
|
||
|
||
/**
|
||
* 将节点移动到头部:先删再头插
|
||
*/
|
||
private void moveToHead(DLinkedNode node) {
|
||
removeNode(node);
|
||
addToHead(node);
|
||
}
|
||
|
||
/**
|
||
* 弹出尾部节点(真尾 = 伪尾的前驱)
|
||
* @return 被移除的节点
|
||
*/
|
||
private DLinkedNode removeTail() {
|
||
DLinkedNode res = tail.prev;
|
||
removeNode(res);
|
||
return res;
|
||
}
|
||
}
|