146. 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. 

链接: http://leetcode.com/problems/lru-cache/

题解: 

设计LRU Cache。从题意理解,基本就是设计一个数据结构,要有capacity,get(key)和set(key)。其中每次get(key)之后,被访问的数据要被更新为最新访问,而set(key)主要就是增加或者更新key指向的数据,还要考虑一下容量。 首先想到用Queue和HashMap来存储数据,结果超时了,因为在Queen里的查找使用了O(n)。 应该用Doubly LinkedList,这样可以在O(1)的时间完成get(key)。 另外看Discuss里,Java本身就提供了LinkedHashMap这种API,虽然算是cheating但可以大大简化代码。有关Java的API还是要仔细读一读。还有就是OO Design,Design Patterns,多线程,都要开始学习了。

想一想,这种题,主要就是如何用合适数据结构来存储数据。下面是一些分析:

  • Cache hit/miss: 我们要使用一个HashMap来达到O(1)的查找效果
  • Get: 我们需要使用一个Doubly Linked List,保存一个自定义结构Node,Node包含key, value, prevNode以及nextNode,主要使用remove以及addLast。最好再使用两个Node - head和tail,用来保存表头和表尾两个节点。
    • 假如miss,返回 -1
    • 假如hit
      • 记录当前结点的value
      • 在LinkdedList中remove当前的这个Node(连接前后节点)
      • 在LinkedList尾部重新加入这个节点,并且更新这个结点的prev节点为LinkedList的前队尾,更新前队尾的next为当前节点,更新当前节点的next为null
  • Set: 跟Get很像,不过要加入一些额外的判断。
    • Cache hit: 和get的hit几乎一样,只不过要增加一个步骤,update节点的value值,其余都要做
    • Cache miss:
      • List.size() < Capacity:
        • 增加新节点到队尾
        • 更新前后连接
        • 增加新节点到map
      • List.size() = Capacity
        • 移除队首节点,更新前后连接
        • 按照List.size() < Capacity的做法来一套

主要就是基本操作都要O(1)才行。 接下来根据逻辑写代码。 写是写完了,也AC了,不过又臭又长,很多重复,还有很多不必要的头尾节点判断。二刷时还要多看discuss,以及好好refactor精简一下,也要好好看看韦斯和塞奇威克的LinkedList实现。

Time Complexity - O(1), Space Complexity- O(n)。

public class LRUCache {
    private Map<Integer, Node> map;
    public int capacity;
    private Node head;
    private Node tail;
    public int size;
        
    public LRUCache(int capacity) {
        this.map = new HashMap<>();
        this.capacity = capacity;
        this.size = 0;
    }
    
    public int get(int key) {
        if(!map.containsKey(key)) {
            return -1;
        } else {
            Node node = map.get(key);
            if(head.key == tail.key)
                return node.val;
            if(node.prev == null && node.next == null)
                return node.val;
            if(key == tail.key) {
                return node.val;
            }    
            
            if(key == head.key) {
                if(head.next != null) {
                    head = head.next;
                    head.prev = null;
                }
            } else {
                node.prev.next = node.next;
                node.next.prev = node.prev;    
            }
            tail.next = node;
            node.prev = tail;
            node.next = null;
            tail = node;
            return node.val;
        }
    }
    
    public void set(int key, int value) {
        if(map.containsKey(key)) {
            Node node = map.get(key);
            node.val = value;
            if(head.key == tail.key)
                return;
            if(node.prev == null && node.next == null)
                return;
            if(key == tail.key) {
                return;
            }    
            
            if(key == head.key) {
                if(head.next != null) {
                    head = head.next;
                    head.prev = null;
                }
            } else {
                node.prev.next = node.next;
                node.next.prev = node.prev;    
            }
            tail.next = node;
            node.prev = tail;
            node.next = null;
            tail = node;
            
        } else {                                     //need to add new node
            if(this.size == this.capacity) {
                int headKey = head.key;;
                if(head.key == tail.key) {
                    head = null;
                    tail = null;
                } else {
                    if(head.next != null) {
                        head = head.next;
                        head.prev = null;
                    }
                }
                map.remove(headKey);
                size--;
            }
            
            Node newNode = new Node(key, value);
            map.put(key, newNode);
            if(head == null || tail == null) {
                head = newNode;
                tail = newNode;
                size++;
                return;
            }
            tail.next = newNode;
            newNode.prev = tail;
            newNode.next = null;
            tail = newNode;
            size++;
        }
    }
    
    private class Node {
        public Node prev;
        public Node next;
        public int key;
        public int val;
        
        public Node(int k, int v) {
            this.key = k;
            this.val = v;
        }
    }
}

 

 

题外话:

11/1/2015 - 这几天朋友推荐了琅琊榜看。一看就不可收拾了,下班回家就是三四集,周末也是整天看。也好,赶紧看完了好好继续做题。

11/2/2015 - Node到底是结点还是节点??? 昨天又忙了一天,5点不到就去JFK接老朋友,晚上11点半才到家,累屎了。不过拿到了很多好吃的和一些购买的资料,多谢老友。不过可惜都是在双11前购买的,否则可以打个半折。

 

二刷

又做到这题,已经过了很久很久了... 方法还是跟上面一样,稍微简写了一点。

  1. 主要思路仍然是使用HashMap和Doubly LinkedList。这个Doubly LinkedList我们可以另写一个private class Node。在Node class里面记录前面和后面两个节点,以及key和value。
  2. 我们要保存一个global的map,以及这个双链表的head和tail。
  3. Get操作: 
    1. 当key不在HashMap中返回-1
    2. 当key在HashMap中,我们取得这个node,将node移动到双链表尾部,返回node.val
  4. Set操作:
    1. 当key在HashMap中,我们取得这个node,更新node.val,将node移动到双链表尾部
    2. 否则我们用map.size()和capacity来检查是否cache容量已满
      1. 假如已满,我们从map和双链表中移除首节点,再从尾部添加新节点。
        1. 从双链表中移除使用了head = head.next,然后新的head非空时head.prev = null。
        2. 当新的head为空时,说明之前的head = tail,这时我们也设tail = null。
      2. 否则我们直接从尾部添加新节点
      3. 将新节点加入到map中
  5. 我们可以另写一个moveToTail()方法来将node移动到尾部,或是从尾部添加新节点。这里需要注意的点如下
    1. head和tail均为空,我们直接设置head = node, tail = node并且返回
    2. 当map中包含node.key时,说明这不是一个新节点。我们要先处理其在双链表中的前节点和后节点,再将其加入到双链表尾部并更新tail。要注意如下:
      1. node = tail,我们并不用做改变,直接return
      2. node = head,我们更新head,并且设置新head.prev = null
      3. 否则node既又前节点,又有后节点,我们将node.prev和node.next连接
      4. 执行下一步 (将node加入到双链表尾部并且更新tail)
    3. 否则,这是一个新节点,我们将其加入到双链表尾部,并且更新tail

Java:

Time Complexity - O(1), Space Complexity- O(n)。  n = capacity

public class LRUCache {
    private Map<Integer, Node> map;
    private Node head, tail;
    private int capacity = 0;
    
    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.map = new HashMap<>();
    }
    
    public int get(int key) {
        if (!map.containsKey(key)) return -1;
        Node node = map.get(key);
        moveToTail(node);
        return node.val;
    }
    
    public void set(int key, int value) {
        if (map.containsKey(key)) {
            Node node = map.get(key);
            node.val = value;
            moveToTail(node);
        } else {
            if (map.size() == capacity) {
                map.remove(head.key);
                head = head.next;
                if (head != null) head.prev = null;     
                else tail = null;               
            }
            Node node = new Node(key, value);
            moveToTail(node);
            map.put(key, node);
        }
    }
    
    private void moveToTail(Node node) {
        if (head == null && tail == null) {
            head = node;
            tail = node;
            return;
        }
        
        if (map.containsKey(node.key)) {
            if (node == tail) return;  // node is tail
            
            if (node == head) {    // node is head;
                head = head.next;
                head.prev = null;
            } else {
                node.prev.next = node.next;
                node.next.prev = node.prev;
            }    
        }
        
        node.prev = tail;
        tail.next = node;
        node.next = null;
        tail = node;
    }
    
    private class Node {
        int key;
        int val;
        Node prev;
        Node next;
        
        public Node(int k, int v) {
            this.key = k;
            this.val = v;
        }
    }
}

 

三刷:

简化了一下对head和tail的处理,建立固定的双链表头尾head和node

class LRUCache {
    private Map<Integer, Node> map;
    private Node head, tail;
    private int capacity = 0;
    
    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.map = new HashMap<>(capacity);
        this.head = new Node(-1, 1);
        this.tail = new Node(-1, 1);
        head.next = tail;
        tail.prev = head;
    }
    
    public int get(int key) {
        if (!map.containsKey(key)) return -1;
        Node node = map.get(key);
        moveNodeToTail(node);
        return node.val;
    }
    
    public void put(int key, int value) {
        if (map.containsKey(key)) {
            Node node = map.get(key);
            node.val = value;
            moveNodeToTail(node);
        } else {
            if (map.size() == capacity) removeNode(head.next);
            Node node = new Node(key, value);
            addNodeAtTail(node);
        }
    }
    
    private void removeNode(Node node) {
        map.remove(node.key);
        node.next.prev = node.prev;
        node.prev.next = node.next;
    }
    
    private void addNodeAtTail(Node node) {
        map.put(node.key, node);
        tail.prev.next = node;
        node.prev = tail.prev;
        node.next = tail;
        tail.prev = node;
    }
    
    private void moveNodeToTail(Node node) {
        removeNode(node);
        addNodeAtTail(node);
    }
    
    private class Node {
        int key;
        int val;
        Node prev;
        Node next;
        
        public Node(int k, int v) {
            this.key = k;
            this.val = v;
        }
    }
}

 

 

Reference:

http://www.cnblogs.com/springfor/p/3869393.html

http://algorithmsandme.in/2014/02/least-recently-used-cache/

https://leetcode.com/discuss/26560/java-solution-with-doubly-linked-list-hash-map

https://leetcode.com/discuss/16010/o-1-java-solution

https://leetcode.com/discuss/20139/java-hashtable-double-linked-list-with-touch-of-pseudo-nodes

https://leetcode.com/discuss/13964/accepted-c-solution-296-ms

https://leetcode.com/discuss/8645/my-o-1-solution-in-java

https://leetcode.com/discuss/42891/probably-the-best-java-solution-extend-linkedhashmap

https://leetcode.com/discuss/1188/java-is-linkedhashmap-considered-cheating

https://en.wikipedia.org/wiki/Cache_algorithms

posted @ 2015-05-09 11:29  YRB  阅读(1187)  评论(0编辑  收藏  举报