zoukankan      html  css  js  c++  java
  • [LeetCode#146]LRU Cache

    Problem:

    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.

    Analysis 1:

    The idea behind this problem is really not easy. Anyone who familar with queue and HashMap can figure out the method.
    Big Picture, use following data structure. 
    HashMap<Integer, LRUNode> map
    Queue<LRUNode> queue
    Map is used to quickly identify the LRUNode in the queue, queue is used for achieve "Least Recently Used Policy". Thus we need to combinely use them together for achieving "get" and "set" operation. 
    
    My first implementation has used the Java Lib's implementation of "LinkedList". 
    It works well when the queue is small, but the the size grows, it exceed the time limit.

    Solution 1:

    class LRUNode {
        int key;
        int value;
        public LRUNode(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
        
    public class LRUCache {
        
        HashMap<Integer, LRUNode> map = null;
        List<LRUNode> queue = null;
        int capacity;
        
        public LRUCache(int capacity) {
            this.capacity = capacity;
            this.map = new HashMap<Integer, LRUNode> ();
            this.queue = new LinkedList<LRUNode> ();
        }
        
        public int get(int key) {
            int value = -1;
            if (map.containsKey(key)) {
                LRUNode node = map.get(key);
                value = node.value;
                queue.remove(node);
                LRUNode new_node = new LRUNode(key, value);
                queue.add(new_node);
                map.put(key, new_node);
            }
            return value;
        }
        
        public void set(int key, int value) {
            if (map.containsKey(key)) {
                LRUNode node = map.get(key);
                queue.remove(node);
                LRUNode new_node = new LRUNode(key, value);
                queue.add(new_node);
                map.put(key, new_node);
            } else{
                if (capacity == queue.size()) {
                    LRUNode delete_node = queue.get(0);
                    map.remove(delete_node.key);
                    queue.remove(delete_node);
                }
                LRUNode new_node = new LRUNode(key, value);
                queue.add(new_node);
                map.put(key, new_node);
            }
        }
    }

    Analysis 2:

    The reason of causing the time limit problem is the queue implementation in Java (whether in ArrayList or LinkedList). The operations for a long queue are quite expensive. 
    1. ArrayList
    size(): O(1) there is a constant
    get(index): O(1)
    add(e): O(1)
    remove(index): O(n)
    remove(e): O(n)
    
    2. LinkedList
    size(); O(1) there is a constant
    get(index): O(n)
    add(e): O(1)
    remove(index): O(n)
    remove(e): O(n) (find the element, singly linked list)
    
    For our solution one, we intensively use remove operation for set() and get() operation. the time cost is really really big!!!!
    Can we solve this bottle neck? Yes, use doubly linked list to implement our own queue. In doubly linkedlist:
    1. remove(e), O(1)
    1. add(e), O(1)
    That's exactly what we want right?
    
    
    Solution 2: Doubly linked list.
    LRUNode:
    class LRUNode {
        int key;
        int value;
        LRUNode pre, next; //those two pointers are very important!
        public LRUNode(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
    
    Note: the head pointer always point to first element, and end pointer always point to the last element!
    When we do any operations on Queue, we must not forget to do it also on related HashMap.
    Apparently, we should separate "remove" and  "addHead" operation out, which is the based brick for other operations.
    
    1. add operation. Take care the case when head and end is null (no elements in the queue)
    private void addHead(LRUNode node) {
        node.next = head;
        node.pre = null; 
        if (head != null)
            head.pre = node;
        head = node;
        if (end == null)
            end = head;
    }
    Make the new node to be the head node before the current head node.
    step: 
    a. point the added node's next pointer against current against.
    b. point the current head's(if not null) pre pointer against the added node.
    c. update the head pointer aginst the added node.
    
    
    2. remove operation.
    The remove case is not easy, it should be distinguished against three cases:
    Wrong logic:
    1. node's pre pointer is null, which means it's head node.
    2. node's end pointer is null, which means it's end node.
    3. node has pre pointer and end pointer, which means it is in the middle of linkly list.
    
    You may want to use following logic:
    
    Mistake:
        private void remove(LRUNode node) {
            LRUNode next = node.next;
            LRUNode pre = node.pre;
            if (node == head) {
                next.pre = null;  //the node is head, but not guarantee it has next node.
                head = next;
                return;
            }
            if (node == end) {
                pre.next = null;
                end = pre;
                return;
            }
            pre.next = next;
            next.pre = pre;
        }
    
    This would cause null pointer exception, cause if "head" and "end" are the same pointer. It actually have no pre and no end!!!
    The more robust way to do it is based on checking if node.pre or node.next is null.
    
    //only tackle with pre pointer
    if (node.pre != null) {
        node.pre.next = node;
    } else {
    //do nothing with pre, but remember to update head pointer
        head = node.next;
    }
    
    //only tackle with next pointer
    if (node.next != null) {
        //only need to edit the pointer pointed against node
        node.next.pre = node;
    } else {
        end = node.pre;
    }

    Solution 2:

    class LRUNode {
        int key;
        int value;
        LRUNode pre, next;
        public LRUNode(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
        
    public class LRUCache {
        HashMap<Integer, LRUNode> map = new HashMap<Integer, LRUNode> ();
        LRUNode head = null, end = null;
        int capacity;
        
        public LRUCache(int capacity) {
            this.capacity = capacity;
        }
        
        public int get(int key) {
            int value = -1;
            if (map.containsKey(key)) {
                LRUNode node = map.get(key);
                value = node.value;
                remove(node);
                addHead(node);
            }
            return value;
        }
        
        private void addHead(LRUNode node) {
            node.next = head;
            node.pre = null;
            if (head != null)
                head.pre = node;
            head = node;
            if (end == null)
                end = head;
        }
        
        
        private void remove(LRUNode node) {
            if (node.pre != null) {
                node.pre.next = node.next;
            } else{
                head = node.next;
            }
            if (node.next != null) {
                node.next.pre = node.pre;
            } else{
                end = node.pre;
            }
        }
        
        
        public void set(int key, int value) {
            if (map.containsKey(key)) {
                LRUNode node = map.get(key);
                remove(node);
                LRUNode new_node = new LRUNode(key, value);
                addHead(new_node);
                map.put(key, new_node);
            } else{
                if (capacity == map.size()) {
                    map.remove(end.key);
                    remove(end);
                }
                LRUNode new_node = new LRUNode(key, value);
                addHead(new_node);
                map.put(key, new_node);
            }
        }
    }
  • 相关阅读:
    学习ASP.NET Core(11)-解决跨域问题与程序部署
    学习ASP.NET Core(10)-全局日志与xUnit系统测试
    学习ASP.NET Core(09)-数据塑形与HATEOAS及内容协商
    学习ASP.NET Core(08)-过滤搜索与分页排序
    学习ASP.NET Core(07)-AOP动态代理与日志
    学习ASP.NET Core(06)-Restful与WebAPI
    学习ASP.NET Core(05)-使用Swagger与Jwt认证
    基于NACOS和JAVA反射机制动态更新JAVA静态常量非@Value注解
    DES 加密解密 文件工具类
    springboot-mybatis双数据源配置
  • 原文地址:https://www.cnblogs.com/airwindow/p/4781224.html
Copyright © 2011-2022 走看看