LeetCode 146 - LRU 缓存

 

这是一道考哈希表和双向链表的算法题. 题目是这样的

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

image

image

image

 

这道算法题有几个点,首先很明显,是key-value结构,我们自然会想到用哈希表来实现。它不需要里面的数据有顺序,也就是说里面数据是无序存储的,所以在C++中我们可以用 unordered_map<key,value>来实现. 题干中要求的添加元素,删除元素,根据key修改value值等,都可以做到。但是有一个难点,就是当插入操作超过了它的最大容量时,需要把最久未被使用的那个键值对从unordered_map中移除,还要保证时间复杂度是O(1) => 这点 unordered_map做不到

这里我们要考虑使用双向链表来实现,用双链表时,我们一般会在链表头部和尾部各增加一个哑节点,也就是说使用一个伪头部(dummy head)和伪尾部(dummy tail)来标记界限. => 这样,添加节点和删除节点就不需要检查相邻的节点是否存在

image

使用双向链表时,最新活跃的节点我们放在链表的头部,最不活跃的节点我们放在链表的尾部。所以,当插入一个新的节点导致超过了LRUCache的最大容量时,我们需要做两部操作:

1.  把链表尾部的节点删除

2. 把新插入的节点放在双向链表的头部

那么,你可能会问,为什么要使用双向链表呢,单向链表不行么?  => 因为单向链表时,我们只能有一个伪头部(dummy head), 要想把链表尾部节点删除,就需要从头开始遍历,一直遍历到某个节点的下一个节点指针是nullptr,这个节点就是尾节点,怎么了,这个时间复杂度显然是O(n), 而不会是O(1)  => 所以我们要用双链表

我们使用C++中的无序哈希表 unordered_map, 键就是key, 值就是双链表的节点, 也就是 unordered_map<int, *DLinkedNode>,  双链表的指针定义如下

image

 

代码如下

 

#include <unordered_map>
using namespace std;

struct DLinkNode
{
    int key; //这里放key的目的是,当需要删除尾节点时,可以通过key从哈希表unordered_map中删除对应的节点
    int value; 
    DLinkNode* prev;
    DLinkNode* 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
{
  private:
    DLinkNode* dummyHead;
    DLinkNode* dummyTail;
    int capacity;
    int size;
    unordered_map<int, DLinkNode*> cache;

   public:
         LRUCache(int _capacity) : capacity(_capacity),size(0)
         {
            dummyHead = new DLinkNode();
            dummyTail = new DLinkNode();
            dummyHead->next = dummyTail;
            dummyTail->prev = dummyHead;
         }

         int GetElement(int key)
         {
            if(cache.count(key) > 0)
            {
                DLinkNode* selectedNode = cache[key];
                MoveNodeToHead(selectedNode);
                return selectedNode->value;
            }
            return -1;

         }

         void PutElement(int key, int value)
         {
            if(cache.count(key) > 0)
            {
               DLinkNode* currNode = cache[key];
               currNode->value = value;
               MoveNodeToHead(currNode); //此时这个刚刚更新值的节点变成最近使用的节点,所以移动到链表头部
            }
            else
            {
                DLinkNode* newNode = new DLinkNode(key,value);
                AddNodeToHead(newNode); //将新节点添加到链表头部,表示最近使用过
                cache[key] = newNode;
                size++;
                if(size > capacity)
                {
                    DLinkNode* tailNode = RemoveTail();
                    cache.erase(tailNode->key);
                    delete tailNode; //这里要delete掉尾节点,释放节点占用的内存
                    tailNode = nullptr; //防止悬空指针
                    size--;
                }

            }
         }

         void MoveNodeToHead(DLinkNode* node)
         {
            //move the node to the head of the doubly linked list
            RemoveNode(node);
            AddNodeToHead(node);
         }

         void AddNodeToHead(DLinkNode* node)
         {
            dummyHead->next->prev = node;
            node->prev = dummyHead;    
            node->next = dummyHead->next;
            dummyHead->next = node;
         }
          
         void RemoveNode(DLinkNode* node)
         {
            node->prev->next = node->next;
            node->next->prev = node->prev;
         }

        DLinkNode* RemoveTail()
        {
            if(dummyTail->prev == dummyHead)
            {
                return nullptr;
            }
            DLinkNode* tailNode = dummyTail->prev;
            RemoveNode(tailNode);
            return tailNode;       
        }
};
   

  

image

 

posted on 2026-10-07 17:42  新西兰程序员  阅读(9)  评论(0)    收藏  举报