这道题的难点有两点:
- 数据结构设计+方法抽象
- 数据一致性维护
go 语言程序设计如下
package main
import ("fmt")
type node struct {
key int
val int
pre *node
next *node
}
type lruCache struct {
cap int
cache map[int]*node
head *node
tail *node
}
func NewLRUCache(capacity int ) *lruCache {
if capacity <= 0 {
return nil
}
head, tail := &node{}, &node{}
head.next = tail
tail.pre = head
return &lruCache{
cap : capacity,
cache : make(map[int]*node, capacity),
head: head,
tail: tail,
}
}
func (this *lruCache) remove(node *node) {
node.pre.next = node.next
node.next.pre = node.pre
}
func (this *lruCache) move2Head( node *node) {
node.next = this.head.next
node.pre = this.head
this.head.next = node
node.next.pre = node
}
func (this *lruCache) Get(key int) int {
if v, ok := this.cache[key]; ok {
this.remove(this.cache[key])
this.move2Head(this.cache[key])
return v.val
}
return -1
}
func (this *lruCache) Put(key, val int ) {
if _, ok := this.cache[key]; ok {
this.cache[key].val = val
this.remove(this.cache[key])
this.move2Head(this.cache[key])
}
if len(this.cache) > this.cap {
delete(this.cache, this.tail.pre.key)
this.remove(this.tail.pre)
}
newNode := &node{
key: key,
val: val,
}
this.move2Head(newNode)
this.cache[key] = newNode
}
func main () {
myLru := NewLRUCache(2)
fmt.Println(myLru.Get(3))
myLru.Put(1,1)
myLru.Put(2,2)
fmt.Println(myLru.Get(2))
myLru.Put(4,4)
fmt.Println(myLru.Get(1))
myLru.Put(3,3)
fmt.Println(myLru.Get(1))
fmt.Println(myLru.Get(2))
fmt.Println(myLru.Get(3))
fmt.Println(myLru.Get(4))
}
146. LRU缓存机制
题目:
运用你所掌握的数据结构,设计实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:你是否可以在 O(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
LRU 缓存算法的核心数据结构就是哈希链表,双向链表和哈希表的结合体。哈希表中存着指向链表节点的指针,从而实现链表的快速查找。
解法:
struct linkedNode {
int key;
int val;
linkedNode *pre;
linkedNode *next;
linkedNode(): key(0), val(0), pre(NULL), next(NULL) {}
linkedNode(int _key, int _val): key(_key), val(_val), pre(NULL), next(NULL) {}
}; //有分号
class LRUCache {
private:
int size;
int capacity;
linkedNode *head;
linkedNode *tail;
unordered_map<int, linkedNode*> cache;
void moveToFirst(linkedNode *node) { //将非第一个节点的node拎到head后面
linkedNode *ptr = head->next;
head->next = node;
node->pre->next = node->next;
node->next->pre = node->pre;
node->pre = head;
node->next = ptr;
ptr->pre = node;
}
void insertFirstNode(linkedNode *node) { //插入一个新节点到head后面
linkedNode *ptr = head->next;
head->next = node;
node->pre = head;
node->next = ptr;
ptr->pre = node;
}
void removeLastNode() { //删除最后一个老节点
linkedNode *ptr = tail->pre;
cache.erase(ptr->key); //释放cache中的指针!
ptr->pre->next = tail;
tail->pre = ptr->pre;
delete ptr; //释放内存
}
public:
LRUCache(int _capacity) {
size = 0;
capacity = _capacity;
head = new linkedNode(); //辅助节点,不用考虑增首节点删尾节点时NULL指针
tail = new linkedNode();
head->next = tail;
tail->pre = head;
}
int get(int key) {
if (!cache.count(key)) //hash表中无此节点
return -1;
linkedNode *node = cache[key]; //O(1)找到该节点
if (head->next == node) //node就是head后第一个节点
return node->val;
moveToFirst(node); //不是首节点就拎到首节点处
return node->val;
}
void put(int key, int value) {
if (cache.count(key)) { //hash中有该key
linkedNode *node = cache[key];
node->val = value;
if (head->next != node) //不是首节点就拎到首节点处
moveToFirst(node);
return;
}
linkedNode *newNode = new linkedNode(key, value);
cache[key] = newNode; //将新节点添加到hash中
if (size == capacity) { //需要先删再增
removeLastNode();
insertFirstNode(newNode);
} else { //只插入即可
++size;
insertFirstNode(newNode);
}
}
};
/**
* 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);
*/
460. LFU缓存
题目:
请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。它应该支持以下操作:get 和 put。
get(key) - 如果键存在于缓存中,则获取键的值(总是正数),否则返回 -1。
put(key, value) - 如果键已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量时,则应该在插入新项之前,使最不经常使用的项无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除最久未使用的键。
「项的使用次数」就是自插入该项以来对其调用 get 和 put 函数的次数之和。使用次数会在对应项被移除后置为 0 。
进阶:
你是否可以在 O(1) 时间复杂度内执行两项操作?
解法
struct setNode {
int key;
int val;
int cnt;
int time;
bool operator < (const setNode& node) const {
return cnt == node.cnt ? time < node.time : cnt < node.cnt;
}
};
class LFUCache {
private:
int size;
int capacity;
int time = 0;
unordered_map<int, setNode> cache; //val是节点
set<setNode> treeSet;
public:
LFUCache(int _capacity) {
size = 0;
capacity = _capacity;
}
int get(int key) {
if (capacity == 0)
return -1;
if (!cache.count(key)) //hash表中无此节点
return -1;
setNode node = cache[key]; //O(1)找到该节点
treeSet.erase(node);
node.cnt++;
node.time = ++time;
treeSet.insert(node);
cache[key] = node;
return node.val;
}
void put(int key, int value) {
if (capacity == 0)
return;
if (cache.count(key)) { //hash中有该key
setNode node = cache[key];
treeSet.erase(node);
node.cnt++;
node.time = ++time;
node.val = value;
treeSet.insert(node);
cache[key] = node;
return;
}
if (size == capacity) {
cache.erase(treeSet.begin()->key);
treeSet.erase(treeSet.begin());
} else {
++size;
}
setNode newNode = setNode{key, value, 1, ++time};
cache[key] = newNode;
treeSet.insert(newNode);
}
};
附录:
unordered_map 判断某个键是否存在
1:iterator find ( const key_type& key );
如果key存在,则find返回key对应的迭代器,如果key不存在,则find返回unordered_map::end。因此可以通过map.find(key) == map.end()来判断,key是否存在于当前的unordered_map中。
2:size_type count ( const key_type& key ) const;
count函数用以统计key值在unordered_map中出现的次数。实际上,c++ unordered_map不允许有重复的key。因此,如果key存在,则count返回1,如果不存在,则count返回0。