【LeetCode】LRU Cache

本文详细介绍了如何设计并实现一个最小最近最少使用(LRU)缓存的数据结构,支持get和set操作。通过使用双向链表和哈希表结合的方式,确保高效的缓存管理和更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考链接:

这个博主也刷了不少leetclde,这道题目写的非常精简。本文代码主要参考这一博客


题目描述

Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set.

get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.

set(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.



题目分析

也就是说

设计一个数据结构,支持以下的get和set

get(key) 获取key的value,如果存在,否则返回-1

set(key,value) 设置或者插入key,如果不存在。如果达到最大容量,在插入之前就使用最近最后使用策略去除一个结点。

示例:

LRU(least recently used)最少使用。
假设 序列为 4 3 4 2 3 1 4 2
物理块有3个 则
首轮 4调入内存 4
次轮 3调入内存 3 4
之后 4调入内存 4 3
之后 2调入内存 2 4 3
之后 3调入内存 3 2 4
之后 1调入内存 1 3 2(因为最少使用的是4,所以丢弃4)
之后 4调入内存 4 1 3(原理同上)

最后 2调入内存 2 4 1

这里选用map数据结构<key,ListNode>,是key到listnode的一个映射。使用链表主要用于很快的找到哪个结点需要被取下来。


图片来源:http://blog.youkuaiyun.com/ryfdizuo/article/details/7916996


在写程序时,由于是双向链表,我们会设计成一个环形的链表,这样只要记录一个head,同时我们也知道tail在哪里了。

总结

使用双向链表的优势:

     插入删除非常方便,如果使用单身链表可能还需要记录要操作结点的前一个结点。


链表操作注意事项:

  1. 构成环形链表方便管理
  2. 所有操作增、删需要考虑头结点的特殊性!!!非常重要!!

代码



/*
编译环境CFree 5.0 
*/
 #include 
   
    
#include 
    
using namespace std;

/*
要点1:使用双向链表,插入和删除非常方便,只是定位到某个结点,就可以删除,
		单身链表需要记录pre结点才行
要点2:特殊输入, 
要点3:链表记住头结点的特殊性。

debug记录:
1- 这里写错成mp[key].count,对map不熟悉的原因 
2- 这里竟然写成了 head->pre - curNode; 这样的错误要避免
3- 头结点 
*/
class ListNode1 {
public:
    ListNode1(int _key, int _val):key(_key),val(_val),pre(NULL),next(NULL) {}

    ListNode1 *pre;
    int key;
    int val;
    ListNode1 *next;
};


 /*
调试用 打印环形链表 
*/
void printList(ListNode1 *head,char *name)
{
	if(head == NULL)
		return;
	
	printf("%10s list:",name);
	
	ListNode1* p = head->next;
	printf(" (%d,%d) ",head->key,head->val);
	while(p != NULL && p != head)
	{
		printf(" (%d,%d) ",p->key,p->val);
		p = p->next;
	} 
	printf("\n");
}

class LRUCache{
public:
    LRUCache(int capacity) {
    	this->capacity = capacity;			//this是一个指针 
    	this->size = 0;
    	this->head = NULL;
    }
    
    int get(int key) {
       if(mp.count(key) == 0)				//这里写错成mp[key].count 
       		return -1;
   		int value = mp[key]->val;			//mp[key]是一个 ListNode1*类型 
   		set(key,value);
		return value; 
    }
    
    void set(int key, int value) {
       if(this->capacity < 1) return;
       if(this->capacity == 1)				//直接替换
	   {
	   		mp.clear();
	   		head = new ListNode1(key,value);
			mp[key] = head;
			return; 
	   } 
	   //这个点已经存在
	   if(mp.count(key) > 0)
	   {
	   		//将结点摘下放到头部
			ListNode1 *cur = mp[key];
			cur->val = value;
			if(cur == head)					//判断是否是头结点 
				return;
			//摘下
			cur->pre->next = cur->next;
			cur->next->pre = cur->pre;
			//放到头
			head->pre->next= cur;
			cur->pre = head->pre;
			cur->next = head;
			head->pre = cur;				//这里竟然写成了 head->pre - curNode; 这样的错误要避免 
			head = cur;
			
			return;			 
	   } 
	   if(size < capacity)					//队列不满在头部添加一个结点 
	   {
	   		size++;
	   		ListNode1 *cur = new ListNode1(key,value);
			mp[key] = cur;
			//如果是第一个结点				!!!!
			if(size == 1)
			{				
				cur->next= cur;
				cur->pre= cur;
				head = cur;
				return;
			} 
			
			//放到头
			head->pre->next= cur;
			cur->pre = head->pre;
			cur->next = head;
			head->pre = cur;
			head = cur;	
	   		return; 
	   } 
	   //如果满了,只需要把尾结点信息改成最新,同时把尾结点改成新结点,不需要链表操作
	   ListNode1 *tail = head->pre;
	   //mp.erase(mp.find(tail->key));
	   mp.erase(tail->key);//这种方法和上一种方法都可以 
	   mp[key] = tail;
	   tail->key=key;
	   tail->val=value;
	   head = tail;
	    
    }
private:
    int capacity;
    int size;
    map
     
       mp;
public:
    ListNode1 *head;
};

/*
LRU(least recently used)最少使用。
假设 序列为 4 3 4 2 3 1 4 2
物理块有3个 则
首轮 4调入内存 4
次轮 3调入内存 3 4
之后 4调入内存 4 3
之后 2调入内存 2 4 3
之后 3调入内存 3 2 4
之后 1调入内存 1 3 2(因为最少使用的是4,所以丢弃4)
之后 4调入内存 4 1 3(原理同上)
最后 2调入内存 2 4 1
*/
int main()
{
	int input[8] = {4,3,4,2,3,1,4,2};
	LRUCache lr(3);
	for(int i = 0;i<8;i++)
	{
		lr.set(input[i],i);	
		printf("%d ",input[i]);
		printList(lr.head,"1");
	}
} 

/*
编译环境CFree 5.0 
*/
#include 
      
       
#include 
       
using namespace std;

/*
要点1:使用双向链表,插入和删除非常方便,只是定位到某个结点,就可以删除,
		单身链表需要记录pre结点才行
要点2:特殊输入, 
要点3:链表记住头结点的特殊性。
*/
class ListNode1 {
public:
    ListNode1(int _key, int _val) {
        key = _key;
        val = _val;
        pre = next = NULL;
    }

    ListNode1 *pre;
    int key;
    int val;
    ListNode1 *next;
};


 /*
调试用 打印环形链表 
*/
void printList(ListNode1 *head,char *name)
{
	if(head == NULL)
		return;
	
	printf("%10s list:",name);
	
	ListNode1* p = head->next;
	printf(" (%d,%d) ",head->key,head->val);
	while(p != NULL && p != head)
	{
		printf(" (%d,%d) ",p->key,p->val);
		p = p->next;
	} 
	printf("\n");
}

class LRUCache{
public:
    LRUCache(int capacity) {
        this->capacity = capacity;
        head = NULL;
        size = 0;
    }
    
    int get(int key) {
        if(mp.count(key) == 0)//查找失败 
            return -1;
        int value = mp[key]->val;
        set(key, value);		//如果存在重新set一次 ,才能把这个结点移到头结点 
        return value;
    }
    
    void set(int key, int value) {
        if(capacity == 1) {				//特殊考虑,只有一个结点果直接替换 
            mp.clear();
            head = new ListNode1(key, value);
            mp[key] = head;
            return;
        }
        if(mp.count(key) > 0) {			//这个结点存在 
            ListNode1 *curNode = mp[key];
            curNode->val = value;		//可能对原来的key的value进行修改 
            
            if(curNode == head)			//如果这个结点是头结点则不做处理 
                return;

            curNode->pre->next = curNode->next;
            curNode->next->pre = curNode->pre;

            head->pre->next = curNode;	//这是一个环形双向链表,这一步是把当然结点换到头上去 
            curNode->pre = head->pre;
            curNode->next = head;
            head->pre = curNode;		
            head = curNode;
            
            return;
        }

		//下面是处理结点不存在的情形 
        if(size < capacity) {//还有剩余空间,新建一个结点,挂到链表头            
            size ++;
            ListNode1 *newNode = new ListNode1(key, value);
            mp[key] = newNode;
            if(size == 1) {//如果这是第一个结点 
                newNode->pre = newNode;
                newNode->next = newNode;
                head = newNode;
                return;
            }
            
            head->pre->next = newNode;	//1-前指向new 
            newNode->pre = head->pre;	//2-new前指 
            newNode->next = head;		//3-new后指 
            head->pre = newNode;		//4-后指向new 
            head = newNode;
            return;
        }
        // size >= capacity  这里需要更新一个结点 这时只要从map中删除原来的记录,重新新加一条记录。 
        ListNode1 *tail = head->pre;
        mp.erase(mp.find(tail->key));
        mp[key] = tail;		//不需要删除链表结点,只要改尾结点更新成新的key和value然后尾做头就可以了。 
        tail->key = key;
        tail->val = value;
        head = tail;
    }
private:
    int capacity;
    int size;
    map
        
          mp; public: ListNode1 *head; }; /* LRU(least recently used)最少使用。 假设 序列为 4 3 4 2 3 1 4 2 物理块有3个 则 首轮 4调入内存 4 次轮 3调入内存 3 4 之后 4调入内存 4 3 之后 2调入内存 2 4 3 之后 3调入内存 3 2 4 之后 1调入内存 1 3 2(因为最少使用的是4,所以丢弃4) 之后 4调入内存 4 1 3(原理同上) 最后 2调入内存 2 4 1 */ int main() { int input[8] = {4,3,4,2,3,1,4,2}; LRUCache lr(3); for(int i = 0;i<8;i++) { lr.set(input[i],i); printf("%d ",input[i]); printList(lr.head,"1"); } } 
        
      
     
   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值