2025-06-30 16:55:09 +08:00

165 lines
5.5 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}