146. LRU 缓存

题目链接:146. LRU 缓存

这道题的关键是把笼统的input和get两个需求,拆解成为小的行为。

  1. 先从需要的结构体分析:

    1)lru链表:需要实现一个淘汰最近最久未使用的缓存,那必须要一个链表,至于是双向还是单向,在后面再分析

    2)哈希表:由于题目提到了希望O(1)的时间进行get,只有一个lru链表肯定做不到的,只能遍历,那就是O(n),所以必须还要一个哈希表,通过key快速定位到lru的节点,拿到value。

    因此结构体方面,lru链表+哈希表。这个题目的核心也就是对lru链表的操作、以及lru链表和哈希表的配合(lru链表和哈希表必须保持数据一致,不能哈希表有的key,在lru离没有)。

  2. 分解需要对lru链表进行哪些操作

    1)input:插入到lru链表:在链表未满时,需要插入到链表头。在链表已满时:需要删除尾节点+插入头节点

    同时在对链表的entry进行增加或者删除时,需要配合着对哈希表进行操作。

    删除尾节点时,为了同时从哈希表中删除,所以节点中必须要记录key。

    总结:input需要lru拥有:移除尾节点+插入一个节点到头。这两个函数

    2)get:链表中移除指定节点+插入该节点到头节点。

    结构体方面:由于需要移除链表中任意一个节点,为了不遍历,必须要双向链表,因为单向链表拿不到某个节点的prev。

  3. 综上

    lru中需要的内容:双向指针、key、value

    lru具备的能力:移除指定节点+插入节点到头节点。

    在操作lru时,如果不是移除后立马加入到头节点,就都得同步操作哈希表。

cpp 复制代码
class LRUCache {
private:
    class ListNode {
    public:
        int key; // 这里的key是用来在淘汰的时候,拿到了lru双向链表的节点ListNode*,需要根据这个node去删除哈希表,因此node中必须要记录key值。
        int value;
        ListNode* next;
        ListNode* prev; // 必须得是双向链表,因为想把lru从尾节点移到头,需要置空倒数第二个节点。需要根据尾节点找到倒数第二个节点,所以必须得是双向链表。
        ListNode():next(NULL), prev(NULL){}
        ListNode(int k, int v):key(k), value(v), next(NULL), prev(NULL) {}
    };
    map<int, ListNode*> hashtbl;
    int capacity;
    int size;
    ListNode* head, *tail;


public:
    LRUCache(int capacity) {
        this->capacity = capacity;
        this->size = 0;
        head = new ListNode();
        tail = new ListNode();
        head->next = tail;
        tail->prev = head;
    }

    int get(int key) {
        auto fetched_node = hashtbl.find(key);
        if (fetched_node != hashtbl.end()) {
            lru_move_node_to_head(fetched_node->second);
            return fetched_node->second->value;
        }
        return -1;
    }
    
    void put(int key, int value) {
        map<int, ListNode*>::iterator fetched_node = hashtbl.find(key); // 迭代器的使用方法,如果不会写用auto也行
        if (fetched_node != hashtbl.end()) {
            lru_move_node_to_head(fetched_node->second);
            fetched_node->second->value = value;
            return; // 这里漏掉了返回
        }
        if (size >= capacity) {
            remove_tail();
        }
        ListNode* node = new ListNode(key, value);
        lru_add_to_head(node);
        hashtbl[key] = node;
        size++; //这里漏掉了大小的改变
    }

private:
    void remove_tail() {
        ListNode* removed_node = tail->prev;
        lru_remove_node(removed_node);
        hashtbl.erase(hashtbl.find(removed_node->key));
    }

    void lru_move_node_to_head(ListNode* node) {
        lru_remove_node(node);
        lru_add_to_head(node);
    }

    void lru_add_to_head(ListNode* node) {
        node->next = head->next;
        node->prev = head;
        head->next->prev = node;
        head->next = node;
    }

    void lru_remove_node(ListNode* node) {
        node->prev->next = node->next;
        node->next->prev = node->prev; // 由于lru尾部加了tail节点,保证数据节点肯定不是尾节点,所以才能不判空就访问node->next。
    }
};

/**
 * 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 天前
算法竞赛C++常用的STL
c++·算法
小羊没烦恼!3 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
猎头南楼3 天前
知识社区推荐系统实践:新用户冷启动与长短期兴趣建模的挑战 资深推荐算法工程师
人工智能·深度学习·算法·机器学习
旖旎夜光3 天前
力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解
c++·学习·算法·leetcode·力控
wzdark3 天前
大规模并行计算中的负载均衡算法研究4
算法
Because_of_Her13 天前
并查集-听课笔记
笔记·算法·并查集
码流子3 天前
高速公路安全监测实践:碰撞监测预警+物联网底座,从感知到处置的闭环
大数据·人工智能·物联网·算法·架构
another heaven3 天前
【算法/C++ MD5算法能否逆解码?原理、C++实现与同类哈希算法对比】
c++·算法·哈希算法
wzdark3 天前
从算法设计模式看编程思维的抽象能力4
算法
2601_962218613 天前
万象生鲜系统称重自动多退少补算法解决生鲜非标品痛点
大数据·数据库·人工智能·python·算法