【LeetCode】【哈希表+双向链表】LRU 缓存 思路解析和代码

LRU 缓存

题目链接

个人思路

采用C++的容器,没有手撕双向链表

题意

实现LRU的初始化,读取,写入,分别对应LRUCache()、get()、put()
在这里插入图片描述

用到的理论和技术

  1. 双向链表list的插入、删除、访问、迭代器、auto关键字
  2. map,插入、删除、访问
  3. 类的初始化
  4. LRU机制

思路

LRU(Least Recently Used),最近最少使用算法,是页面置换算法的一种
也叫最近最久未使用算法
LRUCache():初始化内存的大小
get():获取内存中的数据,若不存在返回-1
put():向内存写入数据。

  1. 若数据存在,则更新并访问当前数据,表示当前使用过当前数据;
  2. 如果不存在
    • 内存没有超限,则写入当前数据,表示当前使用过当前数据。
    • 内存超限,删去最近最久未使用的数据,写入当前数据,表示当前使用过当前数据。

数据结构:
下文规定,哈希表map统称键值;输入数据统称key,val

  1. pair<int,int>:输入数据是key,val的形式,数据形式:key,val
  2. 双向链表List<pair<int, int> >,双向链表
  3. map<int, pair<int, int>::iterator>,mp[key]:map的键 == 数据的key,map的值 == 数据 key,val在双向链表中的迭代器

Q1:为什么要用链表
A:由于算法考虑到访问的顺序,因此需要使用链表
Q2:为什么要用双向量表
A:链表头部表示最近使用过,链表尾部表示最近未使用过,因此涉及到头部的插入和尾部的插入与删除
Q3:为什么要用map
A:要保证常规时间内插入删除
Q4:为什么map的值是key-val对所在双向链表中的迭代器
A:便于list.erase(iter),在任意位置删除
Q5:既然哈希表中已经存了 数据的key,为什么链表中还要存数据key-val对呢,只存val不行么?
A:当内存满了,双向链表末尾数据被删除,对应的哈希表中的旧数据也要删除,然而哈希表的删除是根据哈希表的值进行的,链表中只存数据的val,是无法找到对应数据的key

注意

  • list的函数,begin()和end()表示的不是元素,而是迭代器
  • 元素是通过front()和back()访问的
  • end()表示的不是容器最后一个元素的迭代器,而是容器末端下一位置
  • auto访问map时,这里的auto推导出来的类型并不是迭代器类型,而是键值对(pair)类型
    //因此get()不能写成
    //key存在,返回key的val,并把键值对提到链表头部
    auto key_value = *hash[key];//这里的auto推导出来的类型并不是迭代器类型,而是键值对(pair)类型,不能省略
    cache.erase(hash[key]); //cache删除旧数据
    cache.push_front(*hash[key]); //cache把键值对提到链表头部,此处不能用*hash[key]替换auto,此时catche需要的是pair,auto类型转换结果是pair,但*hash[key]结果不是pair
    hash[key] = cache.begin();
    return cache.front().second;
    

个人思路代码

伪代码

// key 映射到 Node(key, val)
HashMap<Integer, Node> map;
// Node(k1, v1) <-> Node(k2, v2)...
DoubleList cache;

int get(int key) {
    if (key 不存在) {
        return -1;
    } else {        
        将数据 (key, val) 提到开头;
        return val;
    }
}

void put(int key, int val) {
    Node x = new Node(key, val);
    if (key 已存在) {
        把旧的数据删除;
        将新节点 x 插入到开头;
    } else {
        if (cache 已满) {
            删除链表的最后一个数据腾位置;
            删除 map 中映射到该数据的键;
        } 
        将新节点 x 插入到开头;
        map 中新建 key 对新节点 x 的映射;
    }
}
class LRUCache {
private:
    int cap;
    list<pair<int, int> > cache; //双向链表,保证头尾节点在常规时间内删除、插入
    //hash的键存储 数据的key,hash的值存储  数据key-val键值对在双向链表中的迭代器
    unordered_map<int, list<pair<int, int> >::iterator> hash; 
public:
    LRUCache(int capacity) {
        cap = capacity;
    }
    
    //获取缓存中的关键字
    int get(int key) {
        if(hash.count(key) == 0){
            //key不存在,返回-1
            return -1;
        }
        else{
            //key存在,返回key的val,并把键值对提到链表头部
            auto key_value = *hash[key];//这里的auto推导出来的类型并不是迭代器类型,而是键值对(pair)类型,不能省略
            cache.erase(hash[key]); //cache删除旧数据
            cache.push_front(key_value); //cache把键值对提到链表头部
            hash[key] = cache.begin();
            return cache.front().second;
        }
    }
    
    //插入缓存
    void put(int key, int value) {
        if(hash.count(key) != 0){
            //key值已存在,把旧数据删除,新数据插入到双向链表头部
            cache.erase(hash[key]);//删去cache中的旧数据
        }
        else{
            //key值不存在
            if(cache.size() >= cap){
                //内存超限,删除双向链表末尾数据,删除hash中的旧数据,新数据插入到双向链表头部
                hash.erase(cache.back().first);//hash删除末尾数据
                // cache.erase(cache.end());//cache删除末尾数据,末尾数据的地址并非cache.end(),如for(iter != mp.end())
                cache.pop_back();
            }
        }
        //新数据插入到双向链表头部
        cache.push_front({key, value});
        hash[key] = cache.begin();
    }
};

/**
 * 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);
 */

参考

作者:labuladong
链接:https://leetcode-cn.com/problems/lru-cache/solution/lru-ce-lue-xiang-jie-he-shi-xian-by-labuladong/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值