LRU的设计与实现(算法村第五关黄金挑战)

146. LRU 缓存 - 力扣(LeetCode)

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。

实现 LRUCache 类:

  • LRUCache(int capacity)正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 getput 必须以 O(1) 的平均时间复杂度运行。

用一张图来理解整个流程

假设内存只能容纳3个页面,按照 7 0 1 2 0 3 0 4 的次序访问页面。假设内存按照栈的方式来描述访问时间:上面是最近访问的,下面是最远时间访问的,那LRU就是这样工作的:

HashMap + 双向链表

java 复制代码
class LRUCache
{
    /**
     * 双向链表的结点
     */
    class Node
    {
        int key;    //结点的标识
        int value;  //结点的值
        Node pre;
        Node next;

        public Node(){}

        public Node(int key, int value)
        {
            this.key = key;
            this.value = value;
        }
    }

    Node dummyHead; //虚拟头结点
    Node dummyTail; //虚拟尾结点
    /**
     * 用于定位结点的HashMap
     */
    HashMap<Integer, Node> hashMap = new HashMap<>();
    int capacity;   //容量大小
    int size; //当前缓存大小

    public LRUCache(int capacity)
    {
        this.capacity = capacity;
        this.size = 0;

        dummyHead = new Node();
        dummyTail = new Node();
        dummyHead.next = dummyTail;
        dummyTail.pre = dummyHead;
    }

    /**
     * 移除结点
     */
    public void remove(Node node)
    {
        //node前驱的后继改为node的后继
        node.pre.next = node.next;
        //node后继的前驱改为node的前驱
        node.next.pre = node.pre;
    }

    /**
     * 将独立的结点添加到链表头部
     */
    public void addToHead(Node node)
    {
        //头插法
        node.pre = dummyHead;
        node.next = dummyHead.next;
        dummyHead.next.pre = node; //原先表首结点的前驱改为node
        dummyHead.next = node;
    }

    /**
     * 将双向链表中的结点移动到表首
     */
    public void moveToHead(Node node)
    {
        remove(node);
        addToHead(node);
    }


    public int get(int key)
    {
        Node node = hashMap.get(key);

        //如果链表没有这个结点,直接返回-1
        if (node == null)
            return -1;

        //存在的话,先将它提到表首,再返回结点的值
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value)
    {
        Node node = hashMap.get(key);

        //如果链表没有这个结点,则生成一个新结点,并放在表首
        if(node == null)
        {
            Node newNode = new Node(key, value);
            addToHead(newNode);

            hashMap.put(key, newNode);  //在哈希表中记录这个新结点

            //只有添加新结点的情况下才会时缓存大小变化
            size++;

            //若put之后超出容量,则在链表中删除尾结点,并在哈希表中除名
            if (size > capacity)
            {
                Node tail = dummyTail.pre;
                remove(tail);

                hashMap.remove(tail.key);
            }
        }
        else    //如果已经存在,则将node提到表首,并更新value值
        {
            moveToHead(node);
            node.value = value;
        }
    }
}
相关推荐
愚润求学9 分钟前
【Linux】进程间通信(一):认识管道
linux·运维·服务器·开发语言·c++·笔记
渴望技术的猿10 分钟前
Windows 本地部署MinerU详细教程
java·windows·python·mineru
diving deep12 分钟前
XML简要介绍
xml·java·后端
Uranus^14 分钟前
深入解析Spring Boot与Redis集成:高效缓存实践
java·spring boot·redis·缓存
小吕学编程16 分钟前
Jackson使用详解
java·javascript·数据库·json
珊瑚里的鱼24 分钟前
【滑动窗口】LeetCode 1658题解 | 将 x 减到 0 的最小操作数
开发语言·c++·笔记·算法·leetcode·stl
落樱弥城28 分钟前
角点特征:从传统算法到深度学习算法演进
人工智能·深度学习·算法
总是难免34 分钟前
设计模式 - 单例模式 - Tips
java·单例模式·设计模式
晚秋大魔王37 分钟前
OpenHarmony 开源鸿蒙南向开发——linux下使用make交叉编译第三方库——wget
java·linux·运维·开发语言·华为·harmonyos
heath ceTide40 分钟前
轻量、优雅、高扩展的事件驱动框架——Hibiscus-Signal
java·开发语言