代码如下
自己实现双向链表的LRU
import java.util.HashMap; class DLinkedNode{ int key; int value; DLinkedNode pre,next; public DLinkedNode(){} public DLinkedNode(int key,int value){ this.key=key; this.value=value; } public static void moveToHead(DLinkedNode head,DLinkedNode node){ node.pre.next=node.next; node.next.pre=node.pre; node.pre=head; node.next=head.next; head.next.pre=node; head.next=node; } public static void addToHead(DLinkedNode head,DLinkedNode node){ node.pre = head; node.next = head.next; head.next.pre=node; head.next=node; } public static DLinkedNode findTailNode(DLinkedNode tail){ DLinkedNode node = tail.pre; node.pre.next=node.next; node.next.pre=node.pre; return node; } } public class LRUCache { private HashMap<Integer,DLinkedNode> map; private int capacity,size=0; private DLinkedNode head,tail; public LRUCache(int capacity){ this.capacity=capacity; map = new HashMap<>(); head = new DLinkedNode(); tail = new DLinkedNode(); head.next=tail; tail.pre=head; } public int get(int key){ if(map.containsKey(key)){ DLinkedNode node = map.get(key); DLinkedNode.moveToHead(head,node); return node.value; } return -1; } public void put(int key,int value){ if(map.containsKey(key)){ DLinkedNode node = map.get(key); node.value=value; DLinkedNode.moveToHead(head,node); } else{ DLinkedNode newNode = new DLinkedNode(key,value); map.put(key,newNode); DLinkedNode.addToHead(head,newNode); size++; if(size>capacity){ map.remove(DLinkedNode.findTailNode(tail).key); size--; } } } }
使用linkedList方法的LRU
class LRUCache{ private int capacity; private HashMap<Integer,Integer> map; private LinkedList<Integer> list; public LRUCache(int capacity){ this.capacity = capacity; map = new HashMap<>(); list=new LinkedList<>(); } public int get (int key){ if(map.containsKey(key)){ list.remove((Integer) key); list.addLast(key); return map.get(key); } return -1; } public void put(int key, int value){ if(map.containsKey(key)){ list.removeFirst(); list.addLast(key); map.put(key,value); return ; } if(list.size()==capacity){ map.remove(list.removeFirst()); map.put(key,value); list.addLast(key); } else{ map.put(key,value); list.addLast(key); } } } public class LRU { class LRUCache extends LinkedHashMap<Integer,Integer>{ private int capacity; public LRUCache(int capacity){ super(capacity,0.75F,true); this.capacity=capacity; } public int get(int key){ return super.getOrDefault(key,-1); } public void put(int key,int value){ super.put(key,value); } @Override public boolean removeEldestEntry(Map.Entry<Integer,Integer> eldest){ return size()>capacity; } } }
注意,list.remove()方法存在的问题:
我们知道,链表和数组相比,最主要的特点就是add和remove的操作是O(1)的。Java中的链表一般使用LinkedList这个类型,数组一般使用ArrayList。它们同时implements了List这个interface,所以都有remove(int index)和remove(Object o)这两个方法。
普通意义上认为链表的remove操作是O(1)的,是因为对于某个给定的节点node,可以将它的前置节点的next直接置为node的下一个。而数组,则需要删除index处的元素,再将后面n个元素前移,所以需要O(n)的时间。
但是,在Java中果真如此吗?
细看JDK的源码,就可以发现,LinkedList的remove(int index)和remove(Object o)这两个方法都做不到O(1)的时间,而是O(n)。这是因为上面说的数据结构中的O(1)时间,是对于某个已经确定的节点。而LinkedList中,首先必须通过一个循环,找到第一个出现的Object o,或者走到index这个位置,再进行操作。也就是,有一个get的过程。
这时,虽然ArrayList的remove(int index)和remove(Object o)也是O(n)时间,但是移动耗费的时间远比LinkedList中往后寻址来的快得多,特别是元素很多的时候。JDK的源码里,这个操作是用System.arraycopy()来做的。所以,这时,LinkedList的最为坑爹的地方,也是最令人不解的地方就出现了——remove的操作居然比ArrayList还慢,而且慢的多!
而且,LinkedList需要内部维护一个数据结构,JDK 6中叫Entry,JDK 7中叫Node,这需要很多额外的内存。所以,除非急切需要LinkedList的Deque功能,任何情况下都应该使用ArrayList。其实,即使要用Deque,也有ArrayDeque。
所以,有时面试会问,在一个LinkedList list的遍历for循环中,不断执行remove(i)操作,时间复杂度是多少?其实是O(n^2),而不是O(n)。但是,如果使用iterator,it.remove()的时间复杂度就是O(1)了,因为这时元素已经给定。并且,for循环中进行remove(i)操作是要影响下标的。remove过后每次i都必须i--。使用iterator可以有效避免这个问题。这里可以看到,虽然for循环比较直观,但是有时iterator还是非常好的。