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