LRU可以算是书本中比较熟悉的算法了,有缓存存在的场景下,基本都会有它的身影。它的作用就是在空间一定的缓存中,淘汰掉最近最少使用的数据,这样就有空间容纳新的数据。
那么假如需要设计一个类似LRU的算法,要支持获取数据get和插入数据put,并且要求时间复杂度在O(1)。
如果get返回-1,代表这个值不在缓存中,否则返回对应的值。
put(k,v)的操作,如果k在缓存中,那就更新。如果不存在就插入,如果缓存容量满了,就把最近没有使用过的数据删掉,再插入。
要求时间复杂度O(1),说明查找的速度和插入的速度都必须很快。
我们还要维护一个最近最久未使用的数据
,但是这个数据一旦被访问过,就不再是最近最久未使用的数据了。
结合这几个要求,链表应该是最符合的数据结构了,但是链表存在一个问题,查找的速度不是O(1)。那就再加一个Map,把所有的链表节点都放到Map中,这样查找的速度就是O(1)。
链表还必须是带尾指针的双向链表,这样删除的操作就会更加的方便。
class LRUCache { //实现一个双向链表 public class DList{ private Node ret; private int size; private Node tail; public DList(){ this.ret = new Node(-1,-1); this.size=0; } public void addFirst(Node node){ if(ret.next==null){ ret.next = node; node.prev = ret; tail = node; size++; return; } Node p = ret.next; node.next =p; ret.next = node; p.prev = node; node.prev = ret; size++; return; } public void remove(Node node){ if(node==tail){ tail = tail.prev ; } Node p = node.prev; Node q = node.next; p.next = q; if(q!=null){ q.prev = p; } size--; } public Node removeLast(){ Node res = tail; remove(tail); return res; } public int size(){ return this.size; } } public class Node { public int key, val; public Node next, prev; public Node(int k, int v) { this.key = k; this.val = v; } } private HashMap<Integer, Node> map; private int cap; private DList cache; public LRUCache(int capacity) { this.cap = capacity; this.map = new HashMap<>(); this.cache = new DList(); } public int get(int key) { if(!map.containsKey(key)){ return -1; } Node node = map.get(key); int val = node.val; put(key,val); return val; } public void put(int key, int value) { Node x = new Node(key, value); if(map.containsKey(key)){ cache.remove(map.get(key)); cache.addFirst(x); map.put(key,x); } else{ if(cap == cache.size()){ Node last = cache.removeLast(); map.remove(last.key); } cache.addFirst(x); map.put(key,x); } } }
design
LRU