LeetCode 146. LRU Cache

简介: 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。

v2-dceebc6124f1dbbdaa0c7d4c136f5a2d_1440w.jpg

Description



Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and put.


get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.


put(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.


Follow up:

Could you do both operations in O(1) time complexity?


Example:


LRUCache cache = new LRUCache( 2 /* capacity */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // returns 1
cache.put(3, 3);    // evicts key 2
cache.get(2);       // returns -1 (not found)
cache.put(4, 4);    // evicts key 1
cache.get(1);       // returns -1 (not found)
cache.get(3);       // returns 3
cache.get(4);       // returns 4


描述



运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。


获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。


写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。


进阶:

你是否可以在 O(1) 时间复杂度内完成这两种操作?


示例:


LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // 返回  1
cache.put(3, 3);    // 该操作会使得密钥 2 作废
cache.get(2);       // 返回 -1 (未找到)
cache.put(4, 4);    // 该操作会使得密钥 1 作废
cache.get(1);       // 返回 -1 (未找到)
cache.get(3);       // 返回  3
cache.get(4);       // 返回  4


思路


  • LRU 最近最久未使用页面置换算法
  • 最近最久未使用算法要求在替换页面时,替换当前已经在内存的所有页面中,使用时间离现在时间最远的页面.
  • 我们使用两种主要的数据结构,字典(哈希表)和双向链表.
  • 双向链表的每一个节点有四个值:key:页面号,value:当前页的内容,prev:当前节点的上一个节点,next当前节点的下一个节点
  • 字典以链表中的key为键,以每个节点的引用为值.
  • 涉及的主要操作有get,和put,为此我们实现两个辅助的内部函数_remove(self,node)和_add(self,node).
  • _remove函数输入参数是需要移除的node节点,此函数实现将node节点从双链表中移除,但并不删除node节点本身.
  • _add函数输入的参数是node,主要的功能是将node添加到双向链表中.
  • get函数:如果当前请求的页面在内存中,则返回页面的值,并且当前页面放到双向链表的尾部(tail节点前面);如果不在则直接返回-1.
  • put函数:如果当前的页面在请求的内存中,将当前节点放到双向链表末尾;如果不在,创建一个新的节点并且放到双向链表末尾(tail节点前面).
  • put函数接下来检查已分配的页面数,如果超过了最大允许的页面数,删除头节点后面的节点(删除链表中的引用并删除节点本身).


# -*- coding: utf-8 -*-
# @Author:             何睿
# @Create Date:        2019-01-13 11:25:33
# @Last Modified by:   何睿
# @Last Modified time: 2019-01-13 13:16:53
# 使用双向链表,存储页面信息,每一个节点都需要连个指针,一个指向前驱结点,一个指向后继节点
# 根据题意key相当于页面号,value相当于页面中的内容
# 链表中的head,tail节点用作辅助节点
class Node:
    def __init__(self, key, value):
        # key相当于请求的页面
        self.key = key
        # value相等于页面中存储的信息
        self.value = value
        # 指向后继节点
        self.next = None
        # 指向前驱结点
        self.prev = None
class LRUCache:
    def __init__(self, capacity):
        """
        :type capacity: int
        """
        # 页面最大容量
        self.capacity = capacity
        # 字典用于存储链表节点的索引,根据key查找页面
        self._dict = dict()
        # 链表头节点
        # 头节点后面的节点是最早使用的
        self.head = Node(0, 0)
        # 链表尾节点
        # 尾节点前面的节点是刚刚才使用过的
        self.tail = Node(0, 0)
        # 让链表头尾相连
        self.head.next = self.tail
        self.tail.prev = self.head
    def get(self, key):
        """
        :type key: int
        :rtype: int
        """
        # 如果要查找的页面已经被分配
        if key in self._dict:
            # 获取当前节点
            node = self._dict[key]
            # 我们将此节点移动到尾节点前面,表示此节点最近被使用过
            # 此操作分两步 1. 在节点在双向链表中删除(只是删除节点引用,不删除节点本身)
            self._remove(node)
            # 将节点添加到双向链表的末尾(tail节点前面)
            self._add(node)
            return node.value
        return -1
    def put(self, key, value):
        """
        :type key: int
        :type value: int
        :rtype: void
        """
        # 如果要添加的页面已经被分配到页面中,将当前节点删除
        # (删除在链表中的指向,并且删除节点本身)
        if key in self._dict:
            self._remove(self._dict[key])
            del self._dict[key]
        # 新建一个节点
        node = Node(key, value)
        # 添加当前节点
        self._add(node)
        # 建立字典,以key为键,节点的引用为值
        self._dict[key] = node
        # 如果当前的最大容量已经超过了给定的容量,则删除head后面的第一个节点(最近最久未被使用)
        if len(self._dict) > self.capacity:
            node = self.head.next
            # 在双线链表中删除节点
            self._remove(node)
            # 删除字典中的引用
            del self._dict[node.key]
            # 删除当前节点本身
            del node
    def _remove(self, node):
        prev = node.prev
        _next = node.next
        prev.next = _next
        _next.prev = prev
        node.prev = None
        node.next = None
    def _add(self, node):
        prev = self.tail.prev
        prev.next = node
        self.tail.prev = node
        node.prev = prev
        node.next = self.tail


源代码文件在这里.


目录
相关文章
|
4月前
|
缓存 算法 索引
LeetCode146:LRU缓存
LeetCode146:LRU缓存
35 1
|
5月前
|
缓存
leetcode-146:LRU 缓存
leetcode-146:LRU 缓存
26 0
|
5月前
|
存储 缓存 算法
☆打卡算法☆LeetCode 146. LRU 缓存 算法解析
☆打卡算法☆LeetCode 146. LRU 缓存 算法解析
|
存储 缓存
图解LeetCode——146. LRU 缓存
图解LeetCode——146. LRU 缓存
106 1
|
缓存
leetcode 146 LRU缓存
leetcode 146 LRU缓存
107 0
leetcode 146 LRU缓存
|
存储 缓存 算法
LeetCode146 手撕LRU算法的2种实现方法
LeetCode146 手撕LRU算法的2种实现方法
251 0
LeetCode146 手撕LRU算法的2种实现方法
|
存储 缓存 算法
「LeetCode」146-LRU缓存⚡️
「LeetCode」146-LRU缓存⚡️
121 0
「LeetCode」146-LRU缓存⚡️
|
缓存 Java C++
【LeetCode146】LRU缓存机制(list+unordered_map)
首先读懂题意: 首先缓存最大容量初始化为capacity这么大,然后实现: put(key,val)存入键值对;get(key)获得键key对应的值val,如果键key不存在则返回-1。
255 0
|
存储 缓存 API
LeetCode——LRU 缓存机制(借助Map)
LeetCode——LRU 缓存机制(借助Map)
93 0
LeetCode——LRU 缓存机制(借助Map)
|
存储 缓存 API
LeetCode——LRU 缓存机制(借助Map)
解决这个问题之前,我们首先要读懂题意,知道什么是LRU缓存机制,LRU缓存机制指的是优先删除那些最久未使用的缓存,在本题中,一个缓存被put或者get都算是一次使用,明白这一点,也就理解了本题的核心题意。
159 0
LeetCode——LRU 缓存机制(借助Map)