leetcode_146 LRU缓存

1. 题意

请你设计并实现一个满足 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) 的平均时间复杂度运行。

2. 题解

单次插入需要 O ( 1 ) O(1) O(1),

查找也需要是 O ( 1 ) O(1) O(1)。

因此我们需要哈希表和双向链表来完成这一操作。

2.1 我的解

直接自己写个双向链表。

同时我们需要维护链表头和尾。

需要注意的是一些异常情况,比如链表空,或者只有一个头节点。

cpp 复制代码
class LRUCache {


public:
    struct LRUNode {
        LRUNode(int k, int v):key(k),value(v) {

        }
        int key;
        int value;
    };

    struct LRUList {
        LRUList *pre;
        LRUList *next;
        LRUNode *node;
    };

    LRUCache(int capacity):max_cap_(capacity){
        
    }
    
    int get(int key) {
        // cout << "get " << key << "\n";
        if ( hs.count(key) ) {
            LRUList *cur = hs[key];
            if ( cur != head) {
                if ( cur == tail) 
                    tail = cur->pre;
            
                cur->pre->next = cur->next;
                if (cur->next)
                    cur->next->pre = cur->pre;
                
                head->pre  = cur;
                cur->next = head;
                cur->pre  = NULL;
                head = cur;                
            }
            return cur->node->value;
        }
        return -1;
    }
    void put(int key, int value) {
        // cout << "put: [ " << key <<", " << value << " ]" << "\n"; 
        if ( hs.count(key) ) {
            LRUList *cur = hs[key];

            cur->node->value = value;

            if (cur == head)
                return;

            if ( cur == tail) {
                tail = cur->pre;
            }

            cur->pre->next = cur->next;
            if (cur->next)
                cur->next->pre = cur->pre;
            
            head->pre  = cur;
            cur->next = head;
            cur->pre  = NULL;
            head = cur;
        }
        else {
            LRUList *cur = new LRUList;
            cur->pre = cur->next = NULL;
            cur->node = new LRUNode(key, value);
            ++cur_cap_;

            hs[key] = cur;

            if (head)
                head->pre = cur;
            if (tail == NULL)
                tail = cur;

            cur->next = head;
            head = cur;

            if ( cur_cap_ > max_cap_) {
                LRUList *del = tail;

                // cout << "del " << del->node->key << "\n";

                hs.erase(del->node->key);

                tail = del->pre;
                if (tail)
                    tail->next = NULL;
                del->pre = NULL;
                del->next = NULL;
                delete del->node;
                delete del;
                --cur_cap_;
            }
        }   
    }
private:
    int max_cap_;
    int cur_cap_{};

    unordered_map<int,LRUList *> hs;
    LRUList *head{};
    LRUList *tail{};
};

/**
 * 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);
 */
2.2 0x3f的解

我看0x3f的解,主要是模块化,还有加了一个哨兵节点就省去了首尾节点的判断。

cpp 复制代码
class LRUCache {
public:
struct LRUNode {

        LRUNode():pre(NULL),next(NULL) {

        }
        LRUNode(int key, int val):k(key),v(val), pre(NULL), next(NULL){
            
        }
        int k;
        int v;
        LRUNode *pre;
        LRUNode *next;
    };
private:
    int cap_;

    unordered_map<int, LRUNode *> key_to_node;
    LRUNode *dumNode;
    
public:

    

    LRUCache(int capacity) : cap_(capacity), dumNode(new LRUNode()) {
        dumNode->pre = dumNode;
        dumNode->next = dumNode;
    }

    void remove(LRUNode *cur) {
        cur->pre->next = cur->next;
        cur->next->pre = cur->pre;
        cur->pre = cur->next = NULL;
    }

    void push_front(LRUNode *cur) { 

        cur->next = dumNode->next;
        cur->pre  = dumNode;

        dumNode->next->pre =cur;
        dumNode->next = cur;
    }

    int get(int key) {
        //cout << "get " << key << "\n";
        auto it = key_to_node.find(key);
        if ( it != key_to_node.end()) {
            LRUNode *cur = it->second;

            remove( cur );
            push_front( cur );

            return cur->v;
        }

        return -1;
    }

    void put(int key, int value) {
        //cout << "put [ " << key << ", " << value << " ]\n";

        auto it = key_to_node.find(key);
        if ( it != key_to_node.end()) {
            LRUNode *cur  = it->second;
            cur->v = value;

            remove( cur );
            push_front( cur );
        }
        else {
            LRUNode *cur = new LRUNode(key, value);
            key_to_node[key] = cur;
            push_front(cur);

            if ( key_to_node.size() > cap_) {
                LRUNode *del_node = dumNode->pre;
                key_to_node.erase( del_node->k);
                remove( del_node);
                delete del_node;
            }
        }
    }
};

还有一种就是使用标准库的双向链表了,不过标准库的api真的感觉好难用!

cpp 复制代码
class LRUCache {


public:
    struct LRUNode {
        LRUNode(int k, int v):key(k),value(v) {

        }
        int key;
        int value;
    };

    struct LRUList {
        LRUList *pre;
        LRUList *next;
        LRUNode *node;
    };

    LRUCache(int capacity):max_cap_(capacity){
        
    }

    int get(int key) {
        // cout << "get " << key << "\n";
        auto it = key_to_it.find(key);
        if ( it  == key_to_it.end()) {
            return -1;
        }
        
        cached_list.splice( cached_list.begin(), cached_list, it->second);

        return cached_list.begin()->value;
    }
    void put(int key, int value) {
        
        auto it = key_to_it.find( key );
        if ( it != key_to_it.end()) {
            it->second->value = value;

            cached_list.splice( cached_list.begin(), cached_list, it->second);
        }
        else {
            auto nNode = new LRUNode(key, value);
            cached_list.push_front( *nNode );
            key_to_it[key] = cached_list.begin();

            if ( cached_list.size() > max_cap_) {
                key_to_it.erase(cached_list.back().key );
                cached_list.pop_back();
            }
        }
    }
private:
    int max_cap_;
    unordered_map<int, list<LRUNode>::iterator> key_to_it;

    list<LRUNode> cached_list;
};

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

3. 参考

0x3f

cpp-splice

相关推荐
执键行天涯2 小时前
idea中已经被git缓存追踪的文件,如何让git重新忽略
git·缓存·intellij-idea
帅帅爱数学3 小时前
DeepMimic论文详细解析:基于示例引导的深度强化学习实现物理仿真角色技能
算法·强化学习
麦兜*3 小时前
Redis 7.0 新特性深度解读:迈向生产级的新纪元
java·数据库·spring boot·redis·spring·spring cloud·缓存
Dream it possible!3 小时前
LeetCode 面试经典 150_哈希表_快乐数(45_202_C++_简单)(哈希表;快慢指针)
leetcode·面试·散列表
IT成长日记4 小时前
【LVS入门宝典】LVS调度算法轮询(RR)深度解析:从原理到实战的公平调度之道
算法·lvs·rr·轮询调度算法
NAGNIP4 小时前
一文搞懂量化、剪枝和知识蒸馏都是什么?
算法
点云SLAM4 小时前
GTSAM 中自定义因子(Custom Factor)的详解和实战示例
算法·机器人·slam·后端优化·gtsam·gtsam自定义因子·因子图
坐吃山猪4 小时前
Redis03-缓存知识点
redis·缓存
萘柰奈5 小时前
LeetCode刷题记录----62.不同路径(Medium)
算法·leetcode·职场和发展