Idiot-maker

  :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

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

 

posted on 2015-04-27 15:13  NickyYe  阅读(580)  评论(0编辑  收藏  举报