2019-01-13

LeetCode 146. LRU Cache.jpg

LeetCode 146. LRU Cache

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

源代码文件在这里.
©本文首发于何睿的博客,欢迎转载,转载需保留文章来源,作者信息和本声明.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容