LRU缓存机制
最近刷题遇到这个问题,甚是喜欢,便想着将自己整个思考的过程拿出来分享一下!
题目:
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?
示例:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得关键字 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得关键字 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
思考过程:
若是不在乎时间复杂度的话,实现的方案有很多种,我们就直接去思考如何O(1)的时间复杂度,大家很快便能想到读取和插入操作O(1)的HashMap,但是题目要求最近最少使用,什么意思呢,就是你读取和插入操作都会使当前数据排在首位,超过容量,末位即会被淘汰,这边大家会联想到链表来实现这个O(1)级别的数据插入和删除操作,但是链表读取某一个数据是O(n),那么将其整体连起来就是实现一个链表,这个链表的读取,和插入,删除的时间复杂度都是O(1)!下面贴代码,里面有较详细的注释:
class LRUCache { class Node { int k;// key int v;// value int a;// ahead 前面节点的key int n;// next 后面节点的key Node(int k, int v, int a, int n) { this.k = k; this.v = v; this.a = a; this.n = n; } } int size;// 当前容量-1 int c;// 最大容量 int nul = Integer.MIN_VALUE;// 其实这边用包装类型好一点,那么上面就都用包装类型,方便表示null Node last; // 末节点 Node cur;// 头节点 HashMap<Integer, Node> kn = new HashMap<>();// key和节点的映射关系 public LRUCache(int c) { this.c = c; size = 0; } public int get(int k) { if (kn.containsKey(k)) {// 当存在 Node node = kn.get(k); if (k != cur.k) {// 不为头 Node a = kn.get(node.a); if (k != last.k) { //不为尾 Node n = kn.get(node.n); a.n = n.k; n.a = a.k; } else { a.n = nul; last = a; } cur.a = node.k; node.n = cur.k; cur = node; //置为头 return node.v; } else { //为头直接返回 return node.v; } } else { return -1; } } public void put(int k, int v) { if (c == 0) // 当容量c设置为0的时候,其实不存在,可忽略校验 return; if (last == null) { // 当末节点为空时,即空表 last = new Node(k, v, nul, nul); kn.put(k, last); size++; cur = last; } else { if (!kn.containsKey(k)) {// 当不存在此key Node node = new Node(k, v, nul, cur.k);// 创建节点,并设置为头节点cur,并进行a,n的处理 cur.a = node.k; node.n = cur.k; cur = node; if (size < c) { size++; // 还有空余容量的时候,当前大小+1 } else { // 否则将最后一个移除,逻辑和实际都要移除掉 int lk = last.k; last = kn.get(last.a); kn.remove(lk);// 实际移除 cur = node; if (last != null) {// 注意小心空指针 last.n = nul; } } kn.put(k, cur);// 实际添加 } else { if (k != cur.k) {// 当存在key,则需要在逻辑上设置其为cur,并更新value Node node = kn.get(k); node.v = v; Node a = kn.get(node.a); if (k != last.k) {// 不是末节点 Node n = kn.get(node.n); a.n = n.k; n.a = a.k; } else { a.n = nul; last = a; } cur.a = node.k; node.n = cur.k; cur = node;// 置为头 } else { cur.v = v;// 恰好为首位,则只跟新value } } } } }
其实后面我了解到这个就是Java里面的LinkedHashMap,可以直接调用这个里面的API直接实现所需要求!大家感兴趣可以去了解一下。
总结:
之前做题,老是觉得没啥实际意义,感觉只是锻炼思维,而模拟出很多不现实的场景,虽然也可以用其中的思路解决日常所遇到的问题,但做起题目来,有点点枯燥,不过能思考出来还是很开心的,这次的这个变赋予了实际意思,感觉题目就很有意思,看评论区的最高赞也是如此(希望leetCode多一些这样的题),解答此题需要一定的数据结构知识,尤其是链表和哈希表,整个过程其实就是实现链表的操作,搭配上哈希表的操作,建议动手先去实现一个链表的增删改查,而不是单纯调用API,再来解决此题!
新人小菜鸟,也希望大家见谅!一起前行!