https://leetcode.com/problems/lru-cache/
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get
and set
.
get(key)
- Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.set(key, value)
- Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.
解题思路:
这题其实不是太难,但是从不到15%的AC率就能看出来,很难bug free。
关键就在于每次get和set一个key的时候,就把它变为最新用过的,这样才能维护出the least recently used item。
若是用list,很容易实现。不过set一个已有的key的时候,需要在list中移除前面的key,然后在最后加上key,保证他是最新用过的。在list中,remove这个方法是要花O(n)的时间来找到一个元素的。所以下面的代码会超时。
public class LRUCache { Map<Integer, Integer> map = new HashMap<Integer, Integer>(); LinkedList<Integer> list = new LinkedList<Integer>(); int capacity; public LRUCache(int capacity) { this.capacity = capacity; } public int get(int key) { if(map.containsKey(key)) { list.remove(new Integer(key)); list.add(key); return map.get(key); } else { return -1; } } public void set(int key, int value) { if(list.size() < capacity) { if(map.containsKey(key)) { list.remove(new Integer(key)); list.add(key); map.put(key, value); } else { list.add(key); map.put(key, value); } } else { if(map.containsKey(key)) { list.remove(new Integer(key)); list.add(key); map.put(key, value); } else { map.remove(list.get(0)); list.remove(0); list.add(key); map.put(key, value); } } } }
那不用想了,题目一定是要O(1)的时间,那么就无外乎map来做了。而且,不能用Java自带的List,因为这个List无法自己指定顺序,就是像链表那样进行O(1)的删除和插入操作。只能定义了一个内部类,借用前面一直使用的ListNode这个class。
这里我维护了几个变量,注释里写了。为了立刻得到key对应的节点,然后删除它,并将它加入到链表尾部,必须维护一个map,value就是key在链表中的前置节点。last就是链表的尾部节点。key可以直接加在后面。
public class LRUCache { public class ListNode { int val; ListNode next; ListNode(int x) { val = x; next = null; } } //保存真正键值对 Map<Integer, Integer> map = new HashMap<Integer, Integer>(); //保存一个key在链表中的前一个节点 Map<Integer, ListNode> nodeMap = new HashMap<Integer, ListNode>(); //dummy节点,时刻位于链表的首位 ListNode dummy = new ListNode(0); //lastNode时候指向链表的最后一个节点,主要为了在它后面add节点用 ListNode lastNode = dummy; int capacity; public LRUCache(int capacity) { this.capacity = capacity; } public int get(int key) { if(map.containsKey(key)) { if(lastNode.val != key) { ListNode pre = nodeMap.get(key); nodeMap.put(pre.next.next.val, pre); lastNode.next = pre.next; pre.next = pre.next.next; lastNode.next.next = null; nodeMap.put(key, lastNode); lastNode = lastNode.next; } return map.get(key); } else { return -1; } } public void set(int key, int value) { if(map.size() < capacity) { ListNode pre = nodeMap.get(key); ListNode next = new ListNode(key); lastNode.next = next; nodeMap.put(key, lastNode); lastNode = next; if(map.containsKey(key)) { nodeMap.put(pre.next.next.val, pre); pre.next = pre.next.next; } map.put(key, value); } else { ListNode pre = nodeMap.get(key); ListNode next = new ListNode(key); lastNode.next = next; nodeMap.put(key, lastNode); lastNode = next; if(map.containsKey(key)) { nodeMap.put(pre.next.next.val, pre); pre.next = pre.next.next; } else { nodeMap.remove(new Integer(dummy.next.val)); map.remove(new Integer(dummy.next.val)); nodeMap.put(dummy.next.next.val, dummy); dummy.next = dummy.next.next; } map.put(key, value); } } }
逻辑并不复杂,代码比较繁琐,比较容易疏漏。特别是nodeMap的维护,在链表发生任意变化的时候,nodeMap都要进行更新,非常容易疏忽。
update
把第一个解法的LinkedList换成ArrayList,居然AC了。。。
好吧,这里只说结论,而且是肯定的。除非用到LinkedList的deque特性,否则永远都尽量使用ArrayList。Java里LinkedList,无论是remove(Object o)还是remove(int index),都要耗时O(n),而不是O(1)。因为需要首先找到这个第一个Object o,或者index的位置。虽然ArrayList的两个remove方法也要耗时O(n),但这里的耗时主要是花在将后面的元素移动上。与LinkedList不断往后寻址相比,要快得多。
而且LinkedList要维护一个复杂的数据结构Entry,要花费很多额外的内存。
于是,在for循环中使用remove,LinkedList就要总共花费O(n^2)了,OMG!但是,使用iterator循环,然后调用it.remove,却只要O(1)的时间,总计就是O(n),因为已经定位到这个元素了。而且iterator还能避免remove后下标变化的问题,这个是要特别注意的。
http://www.blogjava.net/killme2008/archive/2010/09/16/332168.html
http://xiaozu.renren.com/xiaozu/100134/331695692
http://stackoverflow.com/questions/322715/when-to-use-linkedlist-over-arraylist
http://www.exceptionhelp.com/javadetail?articleId=551
public class LRUCache { Map<Integer, Integer> map = new HashMap<Integer, Integer>(); List<Integer> list = new ArrayList<Integer>(); int capacity; public LRUCache(int capacity) { this.capacity = capacity; } public int get(int key) { if(map.containsKey(key)) { list.remove(new Integer(key)); list.add(key); return map.get(key); } else { return -1; } } public void set(int key, int value) { if(list.size() < capacity) { if(map.containsKey(key)) { list.remove(new Integer(key)); list.add(key); map.put(key, value); } else { list.add(key); map.put(key, value); } } else { if(map.containsKey(key)) { list.remove(new Integer(key)); list.add(key); map.put(key, value); } else { map.remove(list.get(0)); list.remove(0); list.add(key); map.put(key, value); } } } }