• [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);
            }
        }
    }
  • 相关阅读:
    rabbitmq 公平分发和消息接收确认(转载)
    rabbitmq 配置多个消费者(转载)
    Spring整合rabbitmq(转载)
    rabbitmq 一些属性
    rabbitmq 持久化 事务 发送确认模式
    TCP中的长连接和短连接(转载)
    rabbitmq 概念
    ZooKeeper介绍(转载)
    npm install 安装依赖报错解决
    ubuntu下安装node.js教程
  • 原文地址:https://www.cnblogs.com/airwindow/p/4781224.html
Copyright © 2020-2023  润新知