【leetcode】146. LRU缓存机制

题目描述

运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制 。
实现 LRUCache 类:

LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
 

示例:

输入
["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. cache是有容量限制的
  2. 需要根据最近是否使用过来排定数据的顺序
  3. get和put都看做是一次访问

为了保证get和put都满足O(1)的时间复杂度,又由于是根据最近是否访问来决定淘汰顺序,最直观的方案是使用队列。但队列有个致命缺陷——只能操作队头元素和队尾元素,这就意味着get()操作时十分麻烦。
另一个经典的思路是双链表+hashmap,双链表的插入删除时间复杂度都是O(1),而hashmap指针取出结点指针保证了get()的时间复杂度为O(1)。
另外有几点需要注意:

  1. 双链表应设置头结点和尾结点,可以避免空链表的边界情况,省去很多麻烦
  2. get()算作一次访问,访问后需要让该结点重新置于链表的尾部(或头部,看怎么设置和理解,下面代码采用和循环链表同样的概念,即表尾进表头出,也就是表尾代表最新访问的结点,表头代表下一个移除的结点)。代码操作也十分容易,只需将结点在链表中移除(不需要delete),然后再插入到表尾即可
  3. 在超出缓存限制需要删除结点时,别忘了delete指针,避免内存泄漏。

代码

struct DLinkNode {
    int key, value;
    DLinkNode *prev, *next;
    DLinkNode(): key(0), value(0), prev(nullptr), next(nullptr) {}
    DLinkNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {}
};

class LRUCache {
public:
    LRUCache(int capacity) {
        this->cap = capacity;
        this->len = 0;
        tail = new DLinkNode;
        head = new DLinkNode;
        tail -> prev = head;
        head -> next = tail;
    }
    
    int get(int key) {
        if(hashmap.count(key)) {
            DLinkNode* node = hashmap[key];
            movetoTail(node);
            return node -> value;
        }
        return -1;
    }
    
    void put(int key, int value) {
        if(!hashmap.count(key)) {
            DLinkNode* node = new DLinkNode(key, value);
            addNode(node);
            hashmap[key] = node;
            len++;
            if(len > cap) {
                removeNode(head -> next);
                len--;
            }
        }
        else {
            hashmap[key] -> value = value;
            movetoTail(hashmap[key]);
        }
    }
    void movetoTail(DLinkNode* node) {
        node -> prev -> next = node -> next;
        node -> next -> prev = node -> prev;
        tail -> prev -> next = node;
        node -> prev = tail -> prev;
        tail -> prev = node;
        node -> next = tail;
    }
    void addNode(DLinkNode* node) {
        tail -> prev -> next = node;
        node -> prev = tail -> prev;
        node -> next = tail;
        tail -> prev = node;
    }
    void removeNode(DLinkNode* node) {
        node -> prev -> next = node -> next;
        node -> next -> prev = node -> prev;
        hashmap.erase(node->key);
        delete node;
    }
private:
    unordered_map hashmap;
    DLinkNode *head, *tail;
    int cap;
    int len;
};

你可能感兴趣的:(算法c++)