zoukankan      html  css  js  c++  java
  • [LeetCode] 146. LRU Cache(LRU 缓存)

    Description

    Design a data structure that follows the constraints of a Least Recently Used (LRU) cache.
    设计一个数据结构,使其满足 LRU 缓存的限制。

    Implement the LRUCache class:
    实现 LRUCache 类:

    • LRUCache(int capacity) Initialize the LRU cache with positive size capacity.
      LRUCache(int capacity) 用大于 0 的整数 capacity 初始化 LRU 缓存。
    • int get(int key) Return the value of the key if the key exists, otherwise return -1.
      int get(int key) 如果 key 存在,返回 key 对应的 value,否则返回 -1
    • void put(int key, int value) Update the value of the key if the key exists. Otherwise, add the key-value pair to the cache. If the number of keys exceeds the capacity from this operation, evict the least recently used key.
      void put(int key, int value)key 存在,更新 key 对应的 value,否则,将该 key-value 对加入缓存。如果 key 的数量超过了 capacity 的限制,则将最近最少使用的 key 清出缓存。

    Follow up

    Could you do get and put in O(1) time capacity?
    getput 操作,你能使用 O(1) 时间完成吗?

    Example

    Input
    ["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
    [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
    Output
    [null, null, null, 1, null, -1, null, -1, 3, 4]
    
    Explanation
    LRUCache lRUCache = new LRUCache(2);
    lRUCache.put(1, 1); // cache is {1=1}
    lRUCache.put(2, 2); // cache is {1=1, 2=2}
    lRUCache.get(1);    // return 1
    lRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3}
    lRUCache.get(2);    // returns -1 (not found)
    lRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3}
    lRUCache.get(1);    // return -1 (not found)
    lRUCache.get(3);    // return 3
    lRUCache.get(4);    // return 4
    

    Constraints

    • 1 <= capacity <= 3000
    • 0 <= key <= 3000
    • 0 <= value <= 104
    • At most 3 * 104 calls will be made to get and put.

    Solution

    先介绍一个“作弊”的方法,在 Java/Kotlin (JVM) 的类库里有这样一个类:java.util.LinkedHashMap/kotlin.collections.LinkedHashMap(在 Kotlin (JVM) 里其实就是前者的 alias),其实现是可以做到符合 LRU 的要求的,也被不少第三方库用作 LRUCache 的实现,我们需要做的,就是继承它,然后覆写其中的部分方法,代码如下:

    class LRUCache(val capacity: Int) : LinkedHashMap<Int, Int>(capacity, 0.75f, true) {
    
        override fun get(key: Int): Int {
            return super.get(key)?:-1
        }
    
        override fun removeEldestEntry(eldest: MutableMap.MutableEntry<Int, Int>?): Boolean {
            return size > capacity
        }
    
    }
    
    /**
     * Your LRUCache object will be instantiated and called as such:
     * var obj = LRUCache(capacity)
     * var param_1 = obj.get(key)
     * obj.put(key,value)
     */
    

    其中 LinkedHashMap 的构造方法里有一个参数 accessOrder,将其设置为 true,将其设置为按访问顺序排列,然后重写其中的 removeEldestEntry 方法(当这个方法返回 true 时,执行 putputAll 操作会清除最近最少访问的 entry,父类里此方法固定返回 false)。即可将 LinkedHashMap 作为一个 LRU Cache 来使用。至于 LinkedHashMap 的实现原理,读者可自行阅读源码,或者在网络上搜索解析。

    当然,也有不那么作弊的方法,就是把 LinkedHashMap 手动展开来。相当于同时维护一个 map 和一个节点的双向链表,get 时,把 get 的节点移动到头部;remove 时,从 map 和链表中同时移除该节点;put 时,当容量超出时,把双链表尾部元素清除。代码如下:

    class LRUCache(private val capacity: Int) {
        private val cache = hashMapOf<Int, Node>()
        private var count = 0
        private var head = Node()
        private var tail = Node()
    
        init {
            head.prev = null
            tail.next = null
            
            head.next = tail
            tail.prev = head
        }
    
        fun get(key: Int): Int {
            val node = cache[key] ?: return -1
            this.moveToHead(node)
            return node.value
        }
    
        fun put(key: Int, value: Int) {
            val node = cache[key]
            
            if (node == null) {
                val newNode = Node(key, value)
                this.cache[key] = newNode
                this.addNode(newNode)
                
                this.count++
                
                if (this.count > this.capacity) {
                    val tail = this.popTail()
                    tail?.let { this.cache.remove(it.key) }
                    this.count--
                }
            } else {
                node.value = value
                this.moveToHead(node)
            }
        }
    
        /**
         * 双向链表节点结构
         */
        private data class Node(val key: Int = 0, var value: Int = 0) {
            var prev: Node? = null
            var next: Node? = null
        }
        
        private fun addNode(node: Node) {
            node.prev = head
            node.next = head.next
            
            head.next?.prev = node
            head.next = node
        }
        
        private fun removeNode(node: Node) {
            val prev = node.prev
            val next = node.next
            
            prev?.next = next
            next?.prev = prev
        }
        
        private fun moveToHead(node: Node) {
            this.removeNode(node)
            this.addNode(node)
        }
        
        private fun popTail(): Node? {
            val result = tail.prev
            result?.let { removeNode(it) }
            return result
        }
    }
    
    /**
     * Your LRUCache object will be instantiated and called as such:
     * var obj = LRUCache(capacity)
     * var param_1 = obj.get(key)
     * obj.put(key,value)
     */
    
  • 相关阅读:
    如何在Kubernetes集群动态使用 NAS 持久卷
    如何在Kubernetes集群动态使用 NAS 持久卷
    谷歌浏览器报错Unchecked runtime.lastError: The message port closed before a response was received.。
    优化信息流很麻烦?三招教你轻松搞定
    优化信息流很麻烦?三招教你轻松搞定
    java多线程
    java多线程
    酷狗音乐快速转换MP3格式的方法
    Golang项目部署
    解析HTML、JS与PHP之间的数据传输
  • 原文地址:https://www.cnblogs.com/zhongju/p/14114024.html
Copyright © 2011-2022 走看看