3
无重复字符的最长子串
中等频率: 1033
146
LRU缓存机制
中等频率: 818
206
反转链表
简单频率: 712
215
数组中的第K个最大元素
中等频率: 554
25
K个一组翻转链表
困难频率: 475
15
三数之和
中等频率: 439
53
最大子数组和
中等频率: 354
912
排序数组
中等频率: 327
21
合并两个有序链表
简单频率: 310
5
最长回文子串
中等频率: 308
200
岛屿数量
中等频率: 295
33
搜索旋转排序数组
中等频率: 290
46
全排列
中等频率: 282
88
合并两个有序数组
简单频率: 274
20
有效的括号
简单频率: 267
121
买卖股票的最佳时机
简单频率: 259
236
二叉树的最近公共祖先
中等频率: 254
92
反转链表 II
中等频率: 253
141
环形链表
简单频率: 246
300
最长上升子序列
中等频率: 240
54
螺旋矩阵
中等频率: 239
143
重排链表
中等频率: 230
23
合并K个排序链表
困难频率: 225
415
字符串相加
简单频率: 213
56
合并区间
中等频率: 211
160
相交链表
简单频率: 197
42
接雨水
困难频率: 186
1143
最长公共子序列
中等频率: 174
124
二叉树中的最大路径和
困难频率: 174
93
复原IP地址
中等频率: 171
82
删除排序链表中的重复元素 II
中等频率: 170
19
删除链表的倒数第N个节点
中等频率: 169
142
环形链表 II
中等频率: 165
4
寻找两个正序数组的中位数
困难频率: 158
199
二叉树的右视图
中等频率: 150
165
比较版本号
中等频率: 148
704
二分查找
简单频率: 140
22
括号生成
中等频率: 139
94
二叉树的中序遍历
简单频率: 139
239
滑动窗口最大值
困难频率: 137
69
x 的平方根
简单频率: 135
148
排序链表
中等频率: 135
32
最长有效括号
困难频率: 134
31
下一个排列
中等频率: 132
8
字符串转换整数 (atoi)
中等频率: 129
70
爬楼梯
简单频率: 124
322
零钱兑换
中等频率: 123
43
字符串相乘
中等频率: 120
76
最小覆盖子串
困难频率: 117
41
缺失的第一个正数
困难频率: 108
105
从前序与中序遍历序列构造二叉树
中等频率: 106
78
子集
中等频率: 102
151
翻转字符串里的单词
中等频率: 101
155
最小栈
简单频率: 96
34
在排序数组中查找元素的第一个和最后一个位置
中等频率: 94
394
字符串解码
中等频率: 91
101
对称二叉树
简单频率: 91
39
组合总和
中等频率: 89
470
用 Rand7() 实现 Rand10()
中等频率: 87
64
最小路径和
中等频率: 87
104
二叉树的最大深度
简单频率: 87
110
平衡二叉树
简单频率: 85
144
二叉树的前序遍历
简单频率: 84
48
旋转图像
中等频率: 82
234
回文链表
简单频率: 82
695
岛屿的最大面积
中等频率: 82
122
买卖股票的最佳时机 II
简单频率: 82
240
搜索二维矩阵 II
中等频率: 81
221
最大正方形
中等频率: 81
98
验证二叉搜索树
中等频率: 80
543
二叉树的直径
简单频率: 79
14
最长公共前缀
简单频率: 78
179
最大数
中等频率: 76
113
路径总和 II
中等频率: 76
662
二叉树最大宽度
中等频率: 76
62
不同路径
中等频率: 73
198
打家劫舍
中等频率: 71
152
乘积最大子数组
中等频率: 71
560
和为K的子数组
中等频率: 70
112
路径总和
简单频率: 68
226
翻转二叉树
简单频率: 67
209
长度最小的子数组
中等频率: 67
227
基本计算器 II
中等频率: 67
169
多数元素
简单频率: 67
24
两两交换链表中的节点
中等频率: 66
139
单词拆分
中等频率: 65
283
移动零
简单频率: 64
718
最长重复子数组
中等频率: 64
1
两数之和
简单频率: 5
2
两数相加
中等频率: 4

146. LRU缓存机制

查看题目
中等
设计
哈希表
链表
双向链表
频率: 818

解法lru-cache

时间复杂度:O(1) | 空间复杂度:O(capacity) | 推荐使用

动画演示

当前操作:

LRU缓存可视化

容量: 3 | 当前大小: 0
Head
缓存为空
Tail

操作日志

代码实现


class LRUCache {
    class DLinkedNode {
        int key;
        int value;
        DLinkedNode prev;
        DLinkedNode next;

        public DLinkedNode() {
        }

        public DLinkedNode(int _key, int _value) {
            key = _key;
            value = _value;
        }
    }

    private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
    private int size;
    private int capacity;
    private DLinkedNode head, tail;

    public LRUCache(int capacity) {
        this.size = 0;
        this.capacity = capacity;
        // 使用伪头部和伪尾部节点
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head.next = tail;
        tail.prev = head;
    }

    public int get(int key) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            return -1;
        }
        // 如果 key 存在,先通过哈希表定位,再移到头部
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            // 如果 key 不存在,创建一个新的节点
            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 {
            // 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
            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);
    }

    private DLinkedNode removeTail() {
        DLinkedNode res = tail.prev;
        removeNode(res);
        return res;
    }
}
时间复杂度:O(1)
空间复杂度:O(capacity)
算法思路: LRU (Least Recently Used) 缓存是一种常用的缓存淘汰策略,当缓存容量达到上限时,删除最久未使用的数据。 核心思想: 1. 使用双向链表维护访问顺序 - 链表头部:最近访问的元素 - 链表尾部:最久未访问的元素 - 每次访问或更新时,将节点移到头部 2. 使用哈希表实现 O(1) 查找 - key: 缓存的键 - value: 对应的双向链表节点 - 通过哈希表快速定位节点位置 3. 关键操作: - get(key): 通过哈希表定位节点,将其移到链表头部 - put(key, value): - 如果key存在,更新值并移到头部 - 如果key不存在且容量未满,创建新节点并添加到头部 - 如果key不存在且容量已满,删除尾部节点,创建新节点并添加到头部 复杂度分析: - 时间复杂度:O(1) - get操作:哈希表查找O(1) + 双向链表移动O(1) = O(1) - put操作:哈希表查找O(1) + 双向链表插入/删除O(1) = O(1) - 空间复杂度:O(capacity) - 哈希表存储最多capacity个键值对 - 双向链表存储最多capacity个节点