资讯专栏INFORMATION COLUMN

LRU Cache

Shihira / 174人阅读

摘要:因为是所有两个操作的时间复杂度都必须是。因为使用线性的数据结构,并且表示操作的先后顺序,这样的结构就是链表。我们发现,无论是还是都有两个简单操作组成,从链表中移除,放到链表头部。如果从尾部移除,将不会指向任何点。

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.

这题需要我们设计一个cache, 有get和set两个操作。因为是cache, 所有两个操作的时间复杂度都必须是O(1)。
get(key) -- O(1) 很明显,我们需要用一个hashmap来实现O(1)的操作。
set(key, value) -- O(1) 这里有两种情况,key没出现过,就直接加在head。这里出现一个关键词head。
因为使用线性的数据结构,并且表示操作的先后顺序,这样的结构就是链表。是单链表还是双链表?下面我们模拟一下:
capacity = 3
set(1, 100)
set(2, 200)
set(3, 300)
get(2)
如果是单链表,简单表示如下:
1 -> 2 -> 3 -> null
我们可以得到2并放在头部。但是这里用的单链表,我们无法知道2的前面是什么,2前面的所有点都会脱离整体。所以需要一个双链表。
1 <=> 3 <=> 2 <=> null
我们继续操作,set(4, 400),发现已经达到LRU的容量,需要移除,这时候发现我们需要一个尾部来告诉我们需要移除哪个点。
我们发现,无论是get(key)还是set(key, value)都有两个简单操作组成,从链表中移除,放到链表头部。
可以定义两个helper function: remove(node), setHead(node)。

代码如下,带注释:

public class LRUCache {
    class Node{
        int key;
        int value;
        Node pre;       // point to tail direction
        Node next;      // point to head direction
        public Node(int key, int value){
            this.key = key;
            this.value = value;
        }
    }
    
    int capacity;
    Map map = new HashMap<>();
    Node tail = null;
    Node head = null;
    
    public LRUCache(int capacity) {
        this.capacity = capacity;
    }
    
    public int get(int key) {
        if(map.containsKey(key)){       // remove from LRU and put it to head of LRU
            Node n = map.get(key);
            remove(n);
            setHead(n);
            return n.value;
        }
        return -1;
    }
    
    public void set(int key, int value) {
        if(map.containsKey(key)){           // change node value, remove from LRU and put it to head of LRU
            Node old = map.get(key);
            old.value = value;
            remove(old);
            setHead(old);
        } else {
            Node newNode = new Node(key, value);
            if(capacity == map.size()){     //  remove the tail
                map.remove(tail.key);
                remove(tail);
            }
            setHead(newNode);               // set newNode to head
            map.put(key, newNode);
        }
    }
    
    public void remove(Node n){
        if(n.pre != null) {         // change pre node connection
            n.pre.next = n.next;
        } else {                    // check if it is the tail
            tail = n.next;
        }
        
        if(n.next != null) {        // change next node connection
            n.next.pre = n.pre;
        } else {                    // check if it is the head
            head = n.pre;
        }
    }
    
    public void setHead(Node n){
        n.pre = head;
        n.next = null;
        
        if(head != null) {  // check head exist or Not ?
            head.next = n;
        }
        
        head = n;
        if(tail == null){    // empty LRU, intitailize tail node
            tail = head;
        }
    }
}

使用dummyEnd 和dummyHead可以简化代码。

public class LRUCache {
    
    int capacity;
    Map map;
    Node dummyEnd;
    Node dummyHead;
    int count;
    
    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.count = 0;
        map = new HashMap();
        dummyEnd = new Node(0,0);
        dummyHead = new Node(0,0);
        dummyEnd.next = dummyHead;
        dummyHead.pre = dummyEnd;
    }
    
    public int get(int key) {
        Node node = map.get(key);
        if(node == null) {
            return -1;
        } else {
            remove(node);
            putToHead(node);
            return node.val;
        }
    }
    
    public void put(int key, int value) {
        Node oldNode = map.get(key);
        if(oldNode == null) {
            ++count;
            Node newNode = new Node(key, value);
            map.put(key, newNode);
            putToHead(newNode);
            
            if(count > capacity){
                // 从LRU移除
                // 第一次在这里debug了好久,要先取出nextNode, 不然map里remove的就是错误的点,即dummy.next.next。
                Node nextNode = dummyEnd.next;
                remove(nextNode);
                // 从map移除
                map.remove(nextNode.key);
                --count;
            }
            
        } else {
            // 改变值,先移除,再放入头部
            oldNode.val = value;
            remove(oldNode);
            putToHead(oldNode);
        }
    }
    
    public void putToHead(Node node){
        // 加到头和前一个点的中间
        Node preNode = dummyHead.pre;
        preNode.next = node;
        node.pre = preNode;
        dummyHead.pre = node;
        node.next = dummyHead;
    }
    
    public void remove(Node node){
        // 移除。
        node.next.pre = node.pre;
        node.pre.next = node.next;
        // node 如果从尾部移除,将不会指向任何点。
        node.pre = null;
        node.next = null;
    }
    
    class Node{
        int key, val;
        Node pre, next;
        public Node(int key, int val){
            this.key = key;
            this.val = val;
        }
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66402.html

相关文章

  • NPM酷库:lru-cache 基于内存的缓存管理

    摘要:酷库,每天两分钟,了解一个流行库。而直接将数据保存在程序变量中,最经济快捷。但是这样就会带来一些其他问题,比如缓存更新缓存过期等。用于在内存中管理缓存数据,并且支持算法。可以让程序不依赖任何外部数据库实现缓存管理。 NPM酷库,每天两分钟,了解一个流行NPM库。 为了优化程序性能,我们常常需要奖数据缓存起来,根据实际情况,我们可以将数据存储到磁盘、数据库、redis等。 但是有时候要缓...

    Yumenokanata 评论0 收藏0
  • NPM酷库:lru-cache 基于内存的缓存管理

    摘要:酷库,每天两分钟,了解一个流行库。而直接将数据保存在程序变量中,最经济快捷。但是这样就会带来一些其他问题,比如缓存更新缓存过期等。用于在内存中管理缓存数据,并且支持算法。可以让程序不依赖任何外部数据库实现缓存管理。 NPM酷库,每天两分钟,了解一个流行NPM库。 为了优化程序性能,我们常常需要奖数据缓存起来,根据实际情况,我们可以将数据存储到磁盘、数据库、redis等。 但是有时候要缓...

    LoftySoul 评论0 收藏0
  • 一个线程安全的 lrucache 实现 --- 读 leveldb 源码

    摘要:在阅读的源代码的时候,发现其中的类正是一个线程安全的实现,代码非常优雅。至此一个线程安全的类就已经全部实现,在中使用的缓存是,其实就是聚合多个实例,真正的逻辑都在类中。 缓存是计算机的每一个层次中都是一个非常重要的概念,缓存的存在可以大大提高软件的运行速度。Least Recently Used(lru) cache 即最近最久未使用的缓存,多见与页面置换算法,lru 缓存算法在缓存的...

    widuu 评论0 收藏0
  • 剑指offer/LeetCode146/LintCode134_LRU缓存实现

    摘要:剑指缓存实现声明文章均为本人技术笔记,转载请注明出处解题思路缓存两种功能获取的对应,不存在返回版本版本设置缓存已满,删除最近最久未被使用的节点,添加新节点进缓存缓存未满,节点存在,修改节点不存在,添加新节点进缓存解题思路由于缓存插入和删除 剑指offer/LeetCode146/LintCode134_LRU缓存实现 声明 文章均为本人技术笔记,转载请注明出处[1] https://s...

    you_De 评论0 收藏0
  • go实现LRU cache

    摘要:简介概述缓存资源通常比较昂贵通常数据量较大时会竟可能从较少的缓存满足尽可能多访问这里有一种假设通常最近被访问的数据那么它就有可能会被后续继续访问基于这种假设将所有的数据按访问时间进行排序并按驱逐出旧数据那么存在缓存的数据就为热点数据这样既节 1. LRU简介 1.1 概述 缓存资源通常比较昂贵,通常数据量较大时,会竟可能从较少的缓存满足尽可能多访问,这里有一种假设,通常最近被访问的数据...

    Jackwoo 评论0 收藏0

发表评论

0条评论

Shihira

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<