LRU Cache

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);

            }

        }

    }

}

 

你可能感兴趣的:(cache)