LeetCode HOT 100 —— 146.LRU缓存

news2024/11/18 12:22:13

题目

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类:
LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字 key已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行

在这里插入图片描述

思路

下面给出使用封装好的数据结构实现的代码,但是面试官一般不会看重,比如面试官希望你可以自己实现一个简单的双向链表,而不是使用语言自带的、封装好的数据结构,但是能写出下面的代码,至少说明还是对JDK源码有点了解的。

class LRUCache extends LinkedHashMap<Integer, Integer>{
    private int capacity;
    
    public LRUCache(int capacity) {
        super(capacity, 0.75F, true);//这里的参数都是源码里的,建议去读一读源码,比如capacity代表map的容量,loadFactor = 0.75F 代表加载因子 (默认即可),accessorder = fasle表示按照读取顺序排序,true表示按照插入顺序排序,
        this.capacity = capacity;
    }

    public int get(int key) {
        return super.getOrDefault(key, -1);//LinkedHashMap 中的 getOrDefault(),即当key不存在时会返回默认值 -1
    }

    public void put(int key, int value) {
        super.put(key, value);
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
        return size() > capacity; 
    }
}

哈希表 + 双向链表:

LRU缓存机制可以通过哈希表 + 双向链表实现,用一个哈希表和一个双向链表维护所有在缓存中的键值对

  • 双向链表按照被使用的顺序存储这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的 键值对是最久未使用
  • 哈希表就是普通的哈希映射(HashMap),通过缓存数据的键 映射到其在双向链表的位置

然后,首先用哈希表进行定位,找出缓存项在双向链表中的位置,然后将这个缓存项移动到双向链表的头部,即可在O(1)时间内完成getput操作,具体方法如下:

  • 对于get操作,首先判断key是否存在:
    (1)如果key不存在,返回-1
    (2)如果key存在,则key对应的节点是最近被使用的节点。通过哈希表定位到该节点在双向链表中的位置,并将其移动到双向链表的头部,从而返回该节点的值
  • 对于put操作,首先判断key是否存在
    (1)如果key不存在,使用keyvalue创建一个新的节点,在双向链表的头部添加这个节点,并将key和该节点添加进哈希表,然后判断双向链表的节点数是否超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希表中对应的项
    (2)如果key存在,则与get操作类似,先通过哈希表定位,再将对应的节点的值更新为value,并将该节点移到双向链表的头部

上面的操作中,访问哈希表时间复杂度为O(1),在双向链表的头部添加节点、尾部删除节点的时间复杂度也是O(1),而将节点移到双向链表的头部,可以分成 「删除该节点」和「在双向链表的头部添加节点」这两步,都是O(1)的时间复杂度

注意点:在双向链表的实现中,使用一个虚拟头结点(dummy head)虚拟尾结点(dummy tail)标记界限,这样在添加节点和删除节点的时候就不需要检查相邻的节点是否存在

java代码如下:

public class LRUCache {
	class DLinkedNode {
		int key;
		int value;
		DLinkedNode prev;
		DLinkedNode next;
		public DLinkedNode (){}
		public DLinkedNode (int _key, int _value){
			key = _key;
			value = _value;
		}
	}
		
	private Map<Integer,DLinkedNode> cache = new HashMap<>();
	private int size;
	private int capacity;
	private DLinkedNode head,tail;
	
	public LRUCache(int capacity){
		this.size = 0;
		this.capacity = capacity;
		//使用虚拟头结点和虚拟尾结点
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head.next = tail;
        tail.prev = head;
	}
		
	public int get(int key){
		DLinkedNode node = cache.get(key);
		if(node == null){
			return -1;
		}
		//如果key存在,先通过哈希表定位,再移到头部
		moveToHead(node);
		return node.value;
	}
	
	public void put(int key, int value){
		DLinkedNode node = cache.get(key);
		if(node == null){
			//如果 key 不存在,创建一个新的节点
			DLinkedNode newNode = new DLinkedNode(key, value);
			// 添加进哈希表
			cache.put(key, newNode);
			// 添加至双向链表的头部
			addToHead(newNode);
			size++;
            if (size > capacity) {
                // 如果超出容量,删除双向链表的尾部节点
                DLinkedNode tail = removeTail();
                // 删除哈希表中对应的项
                cache.remove(tail.key);
                size--;
            }
		} else {
			// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
			node.value = value;
			moveToHead(node);
		}
	}

    private void addToHead(DLinkedNode node) {//在双向链表头结点位置插入操作
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }

    private void removeNode(DLinkedNode node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    private void moveToHead(DLinkedNode node) {//移动到头结点处,分成两步,先删除节点,然后再头结点处添加节点
        removeNode(node);
        addToHead(node);
    }

    private DLinkedNode removeTail() {//删除末尾节点
        DLinkedNode res = tail.prev;//因为有虚拟尾结点,所以是删除虚拟尾结点的前一个节点
        removeNode(res);
        return res;
    }
    
}

尾声:

大家可能会疑惑,为什么都去实现双向链表,java不是有LinkedHashMapDeque

因为面试需要造轮子,不是说只是为了应付面试,虽然面试确实需要,但是主要还是对自己对底层学习有更大的帮助,比如那啥java还有sort()排序呢,我们为什么还要学习各种排序捏?其实主要还是为了学习底层原理,尤其是上面手动实现双向链表的思想很重要。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/69918.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TI Lab_SRR学习_3 速度扩展_1 预备知识

首先先了解一下SRR模式下的chirp配置是什么样子,SRR的chirp的配置文件可以看(位置位于toolbox中)C:\mmwave_automotive_toolbox_3_1_0__win\mmwave_automotive_toolbox_3_1_0\labs\lab0002_short_range_radar\src\commonsrr_config_chirp_design_SRR80.h 通过以上代码可以知…

网络编程套接字——UDP

一、基础知识 1.区分源地址、目的地址 &#xff08;1&#xff09;源IP地址和目的地址&#xff1a;最开始的IP地址与送达数据的地址 &#xff08;2&#xff09;源MAC地址和目的MAC地址&#xff1a;相当于上一站的地址与下一站的地址&#xff0c;在不断地变化 socket通信&#…

数据库专辑--SQL分类汇总(group by...with rollup),增加“总计”字段

系列文章 C#底层库–数据库访问帮助类&#xff08;MySQL版&#xff09; 数据库专辑–WITH CHECK OPTION的用法 文章目录系列文章前言一、概念介绍二、测试用例2.1 创建表2.2 初始化数据2.3 数据查询2.4 分析问题2.5 解决问题2.6 推荐另一种写法&#xff0c;使用COALESCE三、用…

如何撰写品牌故事?品牌故事软文撰写技巧分享

你听过哪些有温度的品牌故事&#xff1f;我首先想到的是香奈儿&#xff1a; 我的生活不曾取悦于我&#xff0c;所以我创造了自己的生活。 这是香奈儿的创始人可可香奈儿给世人留下的一句话&#xff0c;也是她一生的真实写照。 她被后人看作女性解放和独立的一个象征&#xf…

查询网站有没有被搜狗收录复杂吗?查询搜狗收录简单的方法

对于网站收录的概念&#xff0c;互联网中或者搜索引擎中已经有大量的相关定义。网站收录&#xff0c;指的是爬虫爬取了网页&#xff0c;并将页面内容数据放入搜索引擎数据库中这一结果。 查询网站有没有被搜狗收录复杂吗&#xff1f; 用网站批量查询工具呀&#xff01;操作超简…

React高级备忘录(生命周期)class component

须知 什么是生命周期?就像人有生老病死,component也有类似这样的概念,了解生命周期可以让我们知道如何在「对」的时间做「对」的事。 — Lieutenant 过! 常用生命周期 可以分为三大部分 创建component (componentDidMount)更新component(componentDidUpdate)销毁compone…

照一次CT,对人体的伤害有多大?终于有医生肯站出来说实话

CT是一种检查身体的方式&#xff0c;对于这项检查项目&#xff0c;一直有都有不好的传言&#xff0c;有的人听说CT有辐射&#xff0c;而且辐射比较大&#xff0c;所以比较排斥。 也有的人听说频繁做CT会致癌&#xff0c;所以不愿意做&#xff0c;还有的人把CT当作筛查癌症的神器…

Spring从入门到精通(二)

文章目录1.动态代理1.1 概念1.2 jdk动态代理&#xff08;重点&#xff09;1.3 基于子类的动态代理&#xff08;了解&#xff09;2.AOP2.1 概念2.2 springAop — 基于AspectJ技术2.2.1 AspectJ使用&#xff08;XML&#xff09;2.2.2 AspectJ使用&#xff08;注解开发&#xff09…

【数据结构】二叉树的实现OJ练习

文章目录前言(一) 二叉树的接口实现构建二叉树前序遍历中序遍历后序遍历层序遍历二叉树的节点个数二叉树叶子节点个数二叉树第K层节点个数二叉树的高度查找指定节点判断完全二叉树销毁二叉树(二) 二叉树基础OJ练习单值二叉树相同的树另一棵树的子树二叉树的前序遍历二叉树的最大…

[oeasy]python0026_刷新时间_延迟时间_time_sleep_死循环_while_True

刷新时间 回忆上次内容 time 是一个 ​​module​ import 他可以做和时间相关的事情time.time() 得到当前时间戳 time.localtime() 得到本地时间元组local为本地 time.asctime() 得到时间日期字符串asc为ascii 简略的写法为 asc_time time.asctime() 在​​time.asctime()​…

python -- PyQt5(designer)中文详细教程(六)控件1

控件1 控件就像是应⽤这座房⼦的⼀块块砖。PyQt5有很多的控件&#xff0c;⽐如按钮&#xff0c;单选框&#xff0c;滑动条&#xff0c;复选框等 等。在本章&#xff0c;我们将介绍⼀些很有⽤的控 件&#xff1a; QCheckBox &#xff0c; ToggleButton &#xff0c; QSlider &a…

关于JavaScript运算符的学习

关于博主每篇博文的浪漫主义 【“仅此105秒&#xff0c;无法超越的绝美画面!&#xff01;”】 https://www.bilibili.com/video/BV1nW4y1x78x/?share_sourcecopy_web&vd_source385ba0043075be7c24c4aeb4aaa73352 “仅此105秒&#xff0c;无法超越的绝美画面!&#xff01;…

应用案例:有源无源电路协同仿真

01 有源无源电路协同仿真 随着电路系统集成度和信号速率的提高&#xff0c;电路中的电磁场效应越来越明显&#xff0c;单纯使用电路分析方法已不能满足仿真评估精度要求&#xff0c;这种情况下必须对问题进行分解&#xff0c;采用三维电磁场全波方法对信号传播路径上的封装与…

[附源码]计算机毕业设计JAVA在线文献查阅系统

[附源码]计算机毕业设计JAVA在线文献查阅系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybati…

C# 拖放操作

一 拖放操作 拖放操作Drag and Drop是两个窗口之间传递数据的一种手段。 1 拖放操作两部分&#xff1a;拖Drag、放Drop 几个术语&#xff1a; ① 源窗口&#xff1a;发起拖拽StartDrag; ② 目标窗口&#xff1a;接受拖放AcceptDraop; ③ 拖拽物&#xff1a;即传输的数据Dat…

ChatGPT有多厉害,影响到谷歌地位?

AI神器ChatGPT 火了。 能直接生成代码、会自动修复bug、在线问诊、模仿莎士比亚风格写作……各种话题都能hold住&#xff0c;它就是OpenAI刚刚推出的——ChatGPT。 有脑洞大开的网友甚至用它来设计游戏&#xff1a;先用ChatGPT生成游戏设定&#xff0c;再用Midjourney出图&…

外贸小白,一直不出单怎么办?

米贸搜今天&#xff0c;试着给新人一些方法和技巧&#xff0c;让你尽快在公司立足&#xff01; 事实上&#xff0c;规定几个月内下单的公司&#xff0c;往往都是平台有投资&#xff0c;去展会了&#xff0c;有大量营销费用的公司。当然&#xff0c;老板急着收回成本。对于有足…

网络基本概念

文章目录前言网络分层原因网络分层模型各层大致用途主机网络层网际层传输层应用层总结前言 在日常开发中&#xff0c;大家总是会或多或少的遇到一些网络通信的相关代码&#xff0c;如http请求调用。但是我们却不知道&#xff0c;数据是怎么从一台计算机到另一台计算机的&#…

opcj-如何通过一个项目征服Java

Java早已经不是高大山的稀世珍品了&#xff0c;程序员也不再是高科技工作者&#xff0c;而被称为码农 &#xff0c;为什么呢&#xff1f;因为Java后台的很多基础技术都已经固定了&#xff0c;也就是说主要你从头到尾学一遍就能会 &#xff0c;淘宝双十一搞不定&#xff0c;但是…

2022-12-07 小米pro路由(R3G) 刷固件 openwrt

环境准备&#xff1a;路由开启SSH 1.先登录小米开发者平台&#xff0c;解开小米路由的SSH http://www.miwifi.com/miwifi_open.html 2.路由连接电脑&#xff0c;通过SSH可直接登录到小米路由 本教程以R3G 为例 第一步: 刷入 BREED 如何刷入breed 不同设备方法不同,可以直接U…