Leetcode.146 LRU 缓存

2023-09-18 11:45:23

题目链接

Leetcode.146 LRU 缓存 mid

题目描述

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

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

函数 g e t get get p u t put put 必须以 O ( 1 ) O(1) O(1) 的平均时间复杂度运行。

示例:

输入
[“LRUCache”, “put”, “put”, “get”, “put”, “get”, “put”, “get”, “get”, “get”]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]

解释 LRUCache lRUCache = new LRUCache(2); lRUCache.put(1, 1); // 缓存是
{1=1} lRUCache.put(2, 2); // 缓存是 {1=1, 2=2} lRUCache.get(1); // 返回
1 lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到) lRUCache.put(4, 4); // 该操作会使得关键字 1
作废,缓存是 {4=4, 3=3} lRUCache.get(1); // 返回 -1 (未找到) lRUCache.get(3);
// 返回 3 lRUCache.get(4); // 返回 4

提示:
  • 1 ≤ c a p a c i t y ≤ 3000 1 \leq capacity \leq 3000 1capacity3000
  • 0 ≤ k e y ≤ 10000 0 \leq key \leq 10000 0key10000
  • 0 ≤ v a l u e ≤ 1 0 5 0 \leq value \leq 10^5 0value105
  • 最多调用 2 ∗ 1 0 5 2 * 10^5 2105 g e t get get p u t put put

解法:双向链表 + 哈希表

我们先设计出双向链表的节点 Node

struct Node{
    Node* prev;
    Node* next;
    int key;
    int val;
    Node(int k,int v){
        key = k;
        val = v;
        prev = nullptr;
        next = nullptr;
    }
};

我们开始设计链表的 API。

struct LinkedList{
    Node* head; //链表头节点(假)
    Node* tail; //链表尾节点(假)
    unordered_map<int,Node*> mp; //根据键值 key 获得对应的节点 node
    int size; //节点数量 , 初始为0
    int capacity; //链表容量,即链表最多能由几个节点,多了的节点就移除
};    

每次我们通过 g e t get get p u t put put 操作节点之后,我们就要将其移动到链表头部,所以我们需要一个节点 node 插入到链表头部的函数 add

void add(Node* node){
        head->next->prev = node;
        node->next = head->next;
        head->next = node;
        node->prev = head;
}

此外,我们需要从链表中删除指定节点 node

void remove(Node* node){
        node->prev->next = node->next;
        node->next->prev = node->prev;
}

当链表中的节点数量 s i z e size size 超过链表容量 c a p a c i t y capacity capacity 时 ,即 s i z e > c a p a c i t y size > capacity size>capacity。我们就需要移除尾部的节点 并且 从 m p mp mp 删除对应的 k e y key key n o d e node node 的关系:

void remove(){
        Node* node = tail->prev; //要删除的是尾部的节点
        remove(node);
        int key = node->key;
        mp.erase(key);
        size--; //移除节点,链表节点数量 - 1
}

对于 g e t get get,如果不存在 k e y key key 对应的节点,直接返回 − 1 -1 1;如果存在 ,返回对应节点 n o d e node node 的值,并且将 n o d e node node 提升到链表头部:

int get(int key){
        if(!mp.count(key)) return -1;
        Node* node = mp[key];
        int ans = node->val;
        //如果此时 node 已经是第一个节点了,就没必要移动了,直接返回node->val
        if(node == head->next) return ans;
        //将 node 移动到链表头部
        remove(node);
        add(node);
        return ans;
}

对于 p u t put put,如果存在 k e y key key 对应的节点,我们更新节点值,然后将节点移动到头部即可;如果不存在,那我们直接插入新的节点 N o d e ( k e y , v a l u e ) Node(key,value) Node(key,value),如果此时超出容量,还要移除尾部的节点:

void put(int key,int value){
        if(mp.count(key)){
            Node* node = mp[key];
            node->val = value;
            if(node == head->next) return;
            remove(node);
            add(node);
            return;
        }
        Node* node = new Node(key,value);
        mp[key] = node;
        add(node);
        size++;
        if(size > capacity) remove();
}

时间复杂度: O ( 1 ) O(1) O(1)

完整代码:

struct Node{
    Node* prev;
    Node* next;
    int key;
    int val;
    Node(int k,int v){
        key = k;
        val = v;
        prev = nullptr;
        next = nullptr;
    }
};

struct LinkedList{
    Node* head;
    Node* tail;
    unordered_map<int,Node*> mp;
    int size;
    int capacity;

    LinkedList(int c){
        head = new Node(-1,-1);
        tail = new Node(-1,-1);
        head->next = tail;
        tail->prev = head;
        size = 0;
        capacity = c;
    }

    void put(int key,int value){
        if(mp.count(key)){
            Node* node = mp[key];
            node->val = value;
            if(node == head->next) return;
            remove(node);
            add(node);
            return;
        }
        Node* node = new Node(key,value);
        mp[key] = node;
        add(node);
        size++;
        if(size > capacity) remove();
    }

    int get(int key){
        if(!mp.count(key)) return -1;
        Node* node = mp[key];
        int ans = node->val;
        if(node == head->next) return ans;
        remove(node);
        add(node);
        return ans;
    }

    void add(Node* node){
        head->next->prev = node;
        node->next = head->next;
        head->next = node;
        node->prev = head;
    }

    void remove(){
        Node* node = tail->prev;
        remove(node);
        int key = node->key;
        mp.erase(key);
        size--;
    }

    void remove(Node* node){
        node->prev->next = node->next;
        node->next->prev = node->prev;
    }
};

class LRUCache {
public:
    LinkedList* list;
    LRUCache(int capacity) {
        list = new LinkedList(capacity);
    }
    
    int get(int key) {
        return list->get(key);
    }
    
    void put(int key, int value) {
        list->put(key,value);
    }
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */
更多推荐

【SpringMVC】自定义注解

【SpringMVC】自定义注解前言1.什么是注解?2.注解的用处3.注解的原理1.1.@Override1.2.@SuppressWarnings2.JDK元注解2.1.@Retention2.2.@Target2.3.@Inherited2.4.@Documented3.自定义注解3.1.自定义注解的分类注解类结语

Map<K,V>的使用和List学习

MapMap是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例化子类有关。对于静态类型的查找来说,一般直接遍历或者用二分查找【不会对区间进行插入和删除操作】而在现实生活中的查找比如:根据姓名查询考试成绩通讯录,即根据姓名查询联系方式不重复集合,即需要先搜索关键字是否已经在集合中注:Map最重要的特性就

【Redis】深入探索 Redis 的数据类型 —— 列表 List

文章目录一、List类型介绍二、List类型相关命令2.1LPUSH和RPUSH、LPUSHX和RPUSHX2.2LPOP和RPOP、BLPOP和BRPOP2.3LRANGE、LINDEX、LINSERT、LLEN2.4列表相关命令总结三、List类型内部编码3.1压缩列表(ziplist)3.2链表(linkedli

ceph分布式存储

目录一、概述二、组件三、架构图四、搭建一、概述ceph是一个统一的分布式存储系统,设计初衷是提供较好的性能、可靠性和可扩展性。特点:1.统一存储虽然ceph底层是一个分布式文件系统,但由于在上层开发了支持对象和块的接口。所以在开源存储软件中,能够一统江湖。至于能不能千秋万代,就不知了。2.高扩展性扩容方便、容量大。能够

前端Vue3+element-plus表单输入框实现Cron表达式校验

页面如下:本来想手写正则表达式校验,结果发现很麻烦,cron表达式组成如下:开发使用框架为vue3+element-plus,于是选择cron-validator依赖。使用步骤如下:1、通过npminstallcron-validator命令安装:2、可以通过package.json文件中看到,已安装成功。3、在你需要

自动化测试的生命周期是什么?

软件测试发展到今日,已经逐渐标准化且能力更强,其流程每天都在发展。测试人员的技术熟练程度对于整个测试阶段的成功来说至关重要。测试不再意味着仅仅发现错误;它的范围已经扩大,从任何开发项目开始就可以看出它的重要性。当谈论起自动化测试生命周期(AutomationTestingLifeCycle)时,大多数人认为这只是SDL

【vue】vue 中插槽的三种类型:

文章目录一、匿名插槽:``二、具名插槽:``三、作用域插槽一、匿名插槽:<slot></slot>1.没有为插槽指定名称2.通过slot标签可以添加匿名插槽3.在使用组件的时候,组件中的内容会填充到所有匿名插槽的位置,所以在封装组件的时候,匿名插槽一般只有一个4.匿名插槽可以设置默认的内容,如果没有传入内容就使用默认内

ceph分布式存储部署

一、概述是一个统一的分布式存储系统,设计初衷是提供较好的性能、可靠性和可扩展性。特点1、统一存储虽然ceph底层是一个分布式文件系统,但由于在上层开发了支持对象和块的接口。所以在开源存储软件中,能够一统江湖。至于能不能千秋万代,就不知了。2、高扩展性扩容方便、容量大。能够管理上千台服务器、EB级的容量。3、可靠性高支持

c++ 模版元编程 基于条件的编译

基于条件的编译是指根据不同的条件选择是否编译某段代码或选择不同的代码路径。在C++的模板元编程中,我们可以利用模板特化和std::enable_if技术来实现基于条件的编译。通过基于条件的编译,我们可以在编译期间根据类型特征或其他条件,决定采取不同的代码路径。这种能力使得我们可以针对不同类型或条件编写更加灵活和通用的代

死锁详细解读

目录死锁(1)一、死锁的定义二、产生死锁的原因三、产生死锁的四个必要条件四、解决死锁的方法死锁(2)第三节死锁避免一、死锁避免的概念二、安全状态与安全序列三、银行家算法第四节、死锁的检测与解除一、死锁的检测和解除二、死锁检测的算法三、解除死锁的方法死锁(3)第五节资源分配图一、资源分配图二、死锁定理第六节哲学家就餐问题

SIEM:网络攻击检测

如果您正在寻找一种能够检测环境中的网络威胁、发送实时警报并自动执行事件响应的网络攻击检测平台,Log360SIEM解决方案可以完成所有这些以及更多,能够准确检测安全威胁并遏制网络攻击。网络攻击检测能力基于规则的攻击检测MITREATT&CK实现来检测APTS基于ML的行为分析基于规则的攻击检测使用从Log360强大的关

热文推荐