题设
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
实现 LRUCache 类:
LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作?
解法1
分析
LRU 算法一般用在缓存淘汰算法中,指的最少最近使用的那部分缓存需要被释放掉以腾出空间存放新到的数据,算法的基本做法就是只要数据被查看或者修改或者被新增,就将它视为最近使用的,当要插入新的数据且容量空间不够时,就将最早前使用的数据移除掉。
可见这中间涉及到了数据的删除,插入 ,查找和修改 ,对于缓存而言
插入操作: 只涉及了数据的插入,若空间不够则同时涉及数据的删除 和 插入
查询操作: 涉及数据的查询 和 删除 同时还要有插入
修改操作: 涉及数据的查询,修改,删除 和 插入
上面的分析可得数据的增删改查一个没落下,要想实现O(1)时间复杂度,能选的数据结构中最接近的就是双向链表。
双向链表在修改,插入 和 删除操作上的时间复杂度都是 O(1) ,但是查询上由于需要遍历,变成了O(n)。 很容易的可以想到,将双向链表配合着散列一起使用,就能将查询的时间复杂度提升为 O(1) ,同时在修改和删除上也能够快速找出目标元素。
实现
class LRUCache {private int cap;private int size = 0;Map<Integer,Node> map = new HashMap<>();// 使用哨兵要求不与输入数据混淆Node head = new Node(-1,-1);Node tail = new Node(-1,-1);class Node{private Node prev;private Node next;int key;int val;public Node(int key ,int val){this.key = key;this.val = val;}}public LRUCache(int capacity) {this.cap = capacity;head.next = tail;tail.prev = head;}public int get(int key) {Node node = map.get(key);if(null == node) return -1;remove(node);addToTail(node);return node.val;}public void put(int key, int value) {if(cap <= 0) return;Node node = map.get(key);if(null == node){capCheck();Node nNode = new Node(key,value);addToTail(nNode);map.put(key,nNode);size++;}else{remove(node);addToTail(node);node.val = value;}}private void remove(Node node){Node prev = node.prev;prev.next = node.next;node.next.prev = prev;}private void addToTail(Node node){node.prev = tail.prev;tail.prev.next = node;tail.prev = node;node.next = tail;}private void capCheck(){if(cap == size){// put时有判断cap 要大于0, 这里一定能移除某个元素map.remove(head.next.key);remove(head.next);size--;}}}/*** Your LRUCache object will be instantiated and called as such:* LRUCache obj = new LRUCache(capacity);* int param_1 = obj.get(key);* obj.put(key,value);*/
