一、前言
标签:HashTable+双向链表(Doubly Linked List)。
问题来源LeetCode 146 难度:中等。
问题链接:https://leetcode-cn.com/problems/lru-cache/
二、题目
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作?
示例1:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得关键字 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得关键字 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
三、思路
要求时间复杂度是O(1),那么读写时间复杂度都需要是O(1)。用HashTable记录数据,用双向链表记录待删除顺序。
四、编码实现
//==========================================================================
/*
* @file : 146_LRUCache.h
* @label : HashTable+双向链表(Doubly Linked List)
* @blogs : https://blog.csdn.net/nie2314550441/article/details/107497313
* @author : niebingyu
* @date : 2020/07/20
* @title : 146.LRU缓存机制
* @purpose : 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
* 获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
* 写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。
* 当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
*
* 进阶: 你是否可以在 O(1) 时间复杂度内完成这两种操作?
*
* 示例1:
* LRUCache cache = new LRUCache( 2 /* 缓存容量 * / );
* cache.put(1, 1);
* cache.put(2, 2);
* cache.get(1); // 返回 1
* cache.put(3, 3); // 该操作会使得关键字 2 作废
* cache.get(2); // 返回 -1 (未找到)
* cache.put(4, 4); // 该操作会使得关键字 1 作废
* cache.get(1); // 返回 -1 (未找到)
* cache.get(3); // 返回 3
* cache.get(4); // 返回 4
*
* 来源:力扣(LeetCode)
* 难度:中等
* 链接:https://leetcode-cn.com/problems/lru-cache
*/
//==========================================================================
#pragma once
#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <assert.h>
using namespace std;
#define NAMESPACE_LRUCACHE namespace NAME_LRUCACHE {
#define NAMESPACE_LRUCACHEEND }
NAMESPACE_LRUCACHE
struct DLinkedNode
{
int key, value;
DLinkedNode* prev;
DLinkedNode* next;
DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {}
DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {}
};
// 哈希表 + 双向链表
class LRUCache
{
private:
unordered_map<int, DLinkedNode*> cache;
DLinkedNode* head;
DLinkedNode* tail;
int size;
int capacity;
public:
LRUCache(int _capacity): capacity(_capacity), size(0)
{
// 使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head->next = tail;
tail->prev = head;
}
int get(int key)
{
if (!cache.count(key))
return -1;
// 如果 key 存在,先通过哈希表定位,再移到头部
DLinkedNode* node = cache[key];
moveToHead(node);
return node->value;
}
void put(int key, int value)
{
if (cache.count(key) == 0)
{
// 如果 key 不存在,创建一个新的节点
DLinkedNode* node = new DLinkedNode(key, value);
// 添加进哈希表
cache[key] = node;
// 添加至双向链表的头部
addToHead(node);
++size;
if (size > capacity)
{
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode* removed = removeTail();
// 删除哈希表中对应的项
cache.erase(removed->key);
// 防止内存泄漏
delete removed;
--size;
}
}
else
{
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
DLinkedNode* node = cache[key];
node->value = value;
moveToHead(node);
}
}
void addToHead(DLinkedNode* node)
{
node->prev = head;
node->next = head->next;
head->next->prev = node;
head->next = node;
}
void removeNode(DLinkedNode* node)
{
node->prev->next = node->next;
node->next->prev = node->prev;
}
void moveToHead(DLinkedNode* node)
{
removeNode(node);
addToHead(node);
}
DLinkedNode* removeTail()
{
DLinkedNode* node = tail->prev;
removeNode(node);
return node;
}
};
以下为测试代码//
// 测试 用例 START
struct STestParam
{
int type; // 0: put; 1: get
int key;
int value;
STestParam(int _type, int _key, int _value = 0):type(_type), key(_key), value(_value){}
};
void test(const char* testName, int capacity, vector<STestParam> param, vector<int> expect)
{
vector<int> result;
LRUCache* pCache = new LRUCache(capacity);
for (auto it : param)
{
if (it.type == 0)
pCache->put(it.key, it.value);
else if (it.type == 1)
result.push_back(pCache->get(it.key));
}
if (result == expect)
cout << testName << ", solution passed." << endl;
else
cout << testName << ", solution failed. " << endl;
// 只是测试,申请的内存没有释放
}
// 测试用例
void Test1()
{
vector<STestParam> param =
{
{0,1,1},
{0,2,2},
{1,1},
{0,3,3},
{1,2},
{0,4,4},
{1,1},
{1,3},
{1,4},
};
int capacity = 2;
vector<int> expect = {1,-1,-1,3,4};
test("Test1()", capacity, param, expect);
}
NAMESPACE_LRUCACHEEND
// 测试 用例 END
//
void LRUCache_Test()
{
cout << "------ start 146.LRU缓存机制 ------" << endl;
NAME_LRUCACHE::Test1();
cout << "------ end 146.LRU缓存机制 --------" << endl;
}
执行结果: