Open azl397985856 opened 11 months ago
Ideas
想要Time complexity小, 又是存pairs, 我直接就考虑的HashMap, 当作cache. 对于如何实现LRU, 我考虑的用另外一个data structure去record visits
如果visited Array 去keep track priority, set的时候是O(1), 但是retrieve的时候, 要遍历array里面的所有elements, 需要O(n)
于是考虑LinkedList, 但是LinkedList的containsKey也需要O(n)时间. 如果要O(1)的话, 可以考虑其他data structure
class LRUCache {
HashMap<Integer, Integer> cache;
int size = 0;
LinkedList<Integer> LRU = new LinkedList<>();
public LRUCache(int capacity) {
cache = new HashMap<Integer, Integer>(capacity);
size = capacity;
}
public int get(int key) {
if(cache.get(key) == null)
return -1;
else {
LRU.remove((Integer) key);
LRU.add(key);
return cache.get(key);
}
}
public void put(int key, int value) {
if(cache.containsKey(key) || cache.size() < size){
LRU.remove((Integer) key);
}
else{
cache.remove(LRU.poll());
}
LRU.add(key);
cache.put(key, value);
}
}
/**
* 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);
*/
题解里面是用Hashmap来encode node存放的位置, 并且把node包装成一个object(里面包括prev, next). 用LinkedList类似data structure去改变head, tail指向.
访问和增删操作都要达到O(1),还要存入对值,这里可以使用unorder_map,每一个key可以访问唯一的链表节点,在链表节点中存储value,维护链表,使最近使用的节点移至表头,在链表超过预设容量时,删除表尾节点及unorded_map的对应成员
struct DList {
int key, value;
DList* prev;
DList* next;
DList() : key(0), value(0), prev(nullptr), next(nullptr) {}
DList(int _key, int _value) : key(_key), value(_value), prev(nullptr), next(nullptr) {}
};
class LRUCache {
private:
unordered_map<int, DList*> cache;
DList* head;
DList* tail;
int size;
int capacity;
public:
LRUCache(int _capacity): capacity(_capacity), size(0){
head = new DList();
tail = new DList();
head->next = tail;
tail->prev = head;
}
int get(int key) {
if(!cache.count(key))
{
return -1;
}
DList* node = cache[key];
MoveToHead(node);
return node->value;
}
void put(int key, int value) {
if(!cache.count(key)) {
DList* node = new DList(key, value);
cache[key] = node;
AddToHead(node);
++size;
if(size > capacity) {
DList* removed = RemoveTail();
cache.erase(removed->key);
delete removed;
--size;
}
}
else {
DList* node = cache[key];
node->value = value;
MoveToHead(node);
}
}
void AddToHead(DList* node) {
node->prev = head;
node->next = head->next;
head->next->prev = node;
head->next = node;
}
void RemoveNode(DList* node) {
node->prev->next = node->next;
node->next->prev = node->prev;
}
void MoveToHead(DList* node) {
RemoveNode(node);
AddToHead(node);
}
DList* RemoveTail() {
DList* node = tail->prev;
RemoveNode(node);
return node;
}
};
class DoubleListNode:
def __init__(self, key, val, prev=None, nxt=None):
self.key = key
self.val = val
self.prev = prev
self.next = nxt
class LRUCache:
'''
a dict key->(val, node) to make sure get takes O(1)
a double linked list to make sure put also takes O(1)
put -> not in the dict, not exceed, create a new node and insert to the front, and add entry to dict
put -> not in the dict, exceed, find the front node to evict, and the rest is the same as above
put -> in the dict, if not exceed the capacity, update the value and insert the node to front
'''
def __init__(self, capacity: int):
self.capacity = capacity
self.head = DoubleListNode(-1, -1)
self.tail = DoubleListNode(-1, -1)
self.head.next = self.tail
self.tail.prev = self.head
self.dict = {}
def get(self, key: int) -> int:
if key in self.dict:
value, node = self.dict[key]
self.moveToFront(node)
return self.dict[key][0]
else:
return -1
def put(self, key: int, value: int) -> None:
if key not in self.dict:
if len(self.dict) == self.capacity:
# evict
evict = self.tail.prev
evict.prev.next = self.tail
self.tail.prev = evict.prev
print(evict.key)
del self.dict[evict.key]
node = DoubleListNode(key, value, self.head, self.head.next)
self.head.next.prev = node
self.head.next = node
self.dict[key] = (value, node)
else:
_, node = self.dict[key]
node.val = value
self.moveToFront(node)
self.dict[key] = (value, node)
def moveToFront(self, node):
node.prev.next = node.next
node.next.prev = node.prev
tmp = self.head.next
self.head.next = node
node.next = tmp
node.prev = self.head
tmp.prev = node
Time: get - O(1), put - O(1) Space: O(N)
class LRUCache:
def __init__(self, capacity: int):
self.data = dict()
self.capacity = capacity
def get(self, key: int) -> int:
# get操作后先pop再插入,相当于进行一次使用
s = self.data.get(key, -1)
if s != -1:
self.data.pop(key)
self.data[key] = s
return s
def put(self, key: int, value: int) -> None:
# key存在,更新,再次进行使用
if key in self.data:
self.data.pop(key)
self.data[key] = value
else:
# 判断容量是否达到capacity,未达到,直接插入
if len(self.data) < self.capacity:
self.data[key] = value
else:
# 达到capacity,将第一个也就是要求的最久未使用的逐出pop
k = next(iter(self.data))
self.data.pop(k)
self.data[key] = value
hash+双向链表
class Node {
constructor(key = 0, value = 0) {
this.key = key;
this.value = value;
this.prev = null;
this.next = null;
}
}
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.dummy = new Node(); // 哨兵节点
this.dummy.prev = this.dummy;
this.dummy.next = this.dummy;
this.keyToNode = new Map();
}
getNode(key) {
if (!this.keyToNode.has(key)) { // 没有这本书
return null;
}
const node = this.keyToNode.get(key); // 有这本书
this.remove(node); // 把这本书抽出来
this.pushFront(node); // 放在最上面
return node;
}
get(key) {
const node = this.getNode(key);
return node ? node.value : -1;
}
put(key, value) {
let node = this.getNode(key);
if (node) { // 有这本书
node.value = value; // 更新 value
return;
}
node = new Node(key, value) // 新书
this.keyToNode.set(key, node);
this.pushFront(node); // 放在最上面
if (this.keyToNode.size > this.capacity) { // 书太多了
const backNode = this.dummy.prev;
this.keyToNode.delete(backNode.key);
this.remove(backNode); // 去掉最后一本书
}
}
// 删除一个节点(抽出一本书)
remove(x) {
x.prev.next = x.next;
x.next.prev = x.prev;
}
// 在链表头添加一个节点(把一本书放在最上面)
pushFront(x) {
x.prev = this.dummy;
x.next = this.dummy.next;
x.prev.next = x;
x.next.prev = x;
}
}
时间复杂度:对于 put 和 get 都是 O(1)O(1)O(1)。
空间复杂度:O(capacity)
设计和实现一个 LRU (Least recent use)
缓存机制
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity: init):
self.maxsize = capacity
self.lrucache = OrderedDict()
def get(self, key: int) -> int:
if key in self.lrucache:
self.lrucache.move_to_end(key)
return self.lrucache.get(key, -1)
def put(self, key: int, value: int) -> None:
if key in self.lrucache:
del self.lrucache[key]
self.lrucache[key] = value
if len(self.lrucache) > self.maxsize:
self.lrucache.popitem(last=False)
双指针
class ListNode:
def __init__(self, key, value):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity: int):
# 键到节点的映射
self.dic = dict()
# 缓存容量
self.capacity = capacity
# 哨兵节点
self.head = ListNode(0, 0)
self.tail = ListNode(-1, -1)
# 双指针
self.head.next = self.tail
self.tail.prev = self.head
def get(self, key: int) -> int:
if key in self.dic:
node = self.dic[key]
self.removeFromList(node)
# 将节点插入到链表头部(因为它刚刚被访问)
self.insertIntoHead(node)
return node.value
else:
return -1
def put(self, key: int, value: int) -> None:
# 类似于get()
if key in self.dic:
node = self.dic[key]
self.removeFromList(node)
self.insertIntoHead(node)
node.value = value
# 如果缓存已满
else:
if len(self.dic) >= self.capacity:
# 从列表尾部删除最近最少使用的节点(同时从字典中删除)
self.removeFromTail()
node = ListNode(key,value)
self.dic[key] = node
# 将新节点插入到链表头部
self.insertIntoHead(node)
# 从链表中删除节点
def removeFromList(self, node):
node.prev.next = node.next
node.next.prev = node.prev
def insertIntoHead(self, node):
headNext = self.head.next
self.head.next = node
node.prev = self.head
node.next = headNext
headNext.prev = node
def removeFromTail(self):
if len(self.dic) == 0: return
tail_node = self.tail.prev
del self.dic[tail_node.key]
self.removeFromList(tail_node)
思路不是很复杂,但是实际实现 code 时候细节很多,很容易出错,需要注意这几点
# hash + Doubly linked list
class LRUCache:
def __init__(self, capacity: int):
self.size = capacity
self.cache = dict()
self.head, self.tail = LinkNode(), LinkNode()
self.head.next, self.tail.pre = self.tail, self.head
def get(self, key: int) -> int:
if key not in self.cache: return -1
node = self.cache[key]
self.remove_node(node)
self.add_tail(node)
def put(self, key: int, value: int) -> None:
if key in self.cache:
self.cache[key].value = value
self.get(key)
else:
if len(self.cache) == self.size:
node = self.head.next # get the head node and remove it
self.remove_node(node)
del self.cache[node.key]
node = LinkNode(key, value)
self.add_tail(node)
self.cache[key] = node
def remove_node(self, node):
pre, nt = node.pre, node.next
pre.next = nt
nt.pre = pre
node.pre = node.next = None
def add_tail(self, node):
pre = self.tail.pre
pre.next = node
node.pre = pre
node.next = self.tail
self.tail.pre = node
# ordereddict
class LRUCache:
#let's assume the fist item is the less used due to orderedDict feature
def __init__(self, capacity: int):
self.capacity = capacity
self.cache = OrderedDict()
def get(self, key: int) -> int:
if key not in self.cache: return -1
self.cache.move_to_end(key)
return self.cache[key]
def put(self, key: int, value: int) -> None:
# need to judge the key existing in dictionary d[k]=v is enough
self.cache[key] = value # add to the end by default
self.cache.move_to_end(key)
if len(self.cache) > self.capacity:
self.cache.popitem(last=False) # pop the first item
Because using the hash table to find the node, the time is constant
分析题目:数据需要频繁的O(1)增删(链表
)、需要通过O(1)查询(哈希表
)
确定数据结构:哈希表+双向链表
注意点:
参考讲义:链表-套路五:设计题
class LRUCache {
class ListNode{
int key;
int val;
ListNode pre;
ListNode next;
public ListNode(int key, int val) {
this.key = key;
this.val = val;
}
public ListNode() {}
}
HashMap<Integer,ListNode> map = new HashMap<>();
ListNode head;
ListNode tail;
int size;
int capacity;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
this.head = new ListNode();
this.tail = new ListNode();
//dummyhead ⇄ ..... ⇄ dummytail
//虚拟头
this.head.next = this.tail;
//虚拟尾
this.tail.pre = this.head;
}
public int get(int key) {
ListNode node = map.get(key);
if (node != null) {
del(node);
addHead(node);
return node.val;
} else {
return -1;
}
}
public void put(int key, int value) {
ListNode node = map.get(key);
if (node != null) {
node.val = value;
map.put(key, node);
del(node);
addHead(node);
} else {
ListNode newNode = new ListNode(key,value);
if (size >= capacity) {
//del tail
map.remove(this.tail.pre.key);
del(this.tail.pre);
this.size--;
}
map.put(key, newNode);
addHead(newNode);
this.size++;
}
}
private void del(ListNode listNode) {
//del
ListNode pre = listNode.pre;
ListNode next = listNode.next;
pre.next = next;
next.pre = pre;
}
private void addHead(ListNode listNode) {
//dummyhead ⇄ otherNode ⇄ dummytail
//dummyhead ⇄ listNode ⇄ otherNode ⇄ dummytail
listNode.pre = this.head;
listNode.next = this.head.next;
this.head.next.pre = listNode;
this.head.next = listNode;
}
}
/**
var creatNode = function(key, value, pre) { return { key: key, value: value, pre: pre || null, next: null } }
var moveNode = function(cur) { if (this.head === this.tail) return; if (!cur.next) { return; } if (cur.pre) { cur.next.pre = cur.pre; cur.pre.next = cur.next; } else { this.head = cur.next; cur.next.pre = null; } cur.next = null; this.tail.next = cur; cur.pre = this.tail; this.tail = cur; }
/**
@return {number} */ LRUCache.prototype.get = function(key) { if (this.length === 0) return -1; let cur = this.hashCache[key]; if (!cur) return -1;
moveNode.call(this, cur); return this.hashCache[key].value; };
/**
@return {void} */ LRUCache.prototype.put = function(key, value) { let cur = this.hashCache[key]; if (cur) { cur.value = value; moveNode.call(this, cur);
} else { this.length++; cur = creatNode(key, value, this.tail); this.hashCache[key] = cur; if (this.length === 1) { this.head = cur; this.tail = cur; return null; } this.tail.next = cur; this.tail = cur; if (this.length > this.capacity) { this.length--; this.hashCache[this.head.key] = undefined; this.head = this.head.next; this.head.pre = null;
}
} };
/**
public class LRUCache {
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {}
public DLinkedNode(int key, int value) {this.key = key; this.value = value;}
}
private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
private int size;
private int capacity;
private DLinkedNode head, tail;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
// 使用伪头部和伪尾部节点
this.head = new DLinkedNode();
this.tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
// 如果 key 不存在,创建一个新的节点
DLinkedNode newNode = new DLinkedNode(key, value);
// 添加进哈希表
cache.put(key, newNode);
// 添加至双向链表的头部
addToHead(newNode);
++size;
if (size > capacity) {
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode tail = removeTail();
// 删除哈希表中对应的项
cache.remove(tail.key);
--size;
}
}
else {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node.value = value;
moveToHead(node);
}
}
private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
}
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
}
class LRUCache { int cap; LinkedHashMap<Integer, Integer> cache = new LinkedHashMap<>(); public LRUCache(int capacity) { this.cap = capacity; }
public int get(int key) {
if (!cache.containsKey(key)) {
return -1;
}
makeRecently(key);
return cache.get(key);
}
public void put(int key, int val) {
if (cache.containsKey(key)) {
cache.put(key, val);
makeRecently(key);
return;
}
if (cache.size() >= this.cap) {
int oldestKey = cache.keySet().iterator().next();
cache.remove(oldestKey);
}
cache.put(key, val);
}
private void makeRecently(int key) {
int val = cache.get(key);
cache.remove(key);
cache.put(key, val);
}
}
题目有要求时间复杂度必须O(1)
,因此不能使用遍历的方式;
首先看了下 LRU缓存
的概念,用链表实现简单来说就是,链表一头是最新的,另一头是最旧的。因此在get
或者 put
时应该把当前放到最新的那一头,而当超出的时候从最旧的那一头移除一个节点即可,同时需要更新哈希表;
所以至少需要一个虚拟头结点和虚拟尾节点,可以直接获取到 使用频率最高或最低的节点
,那么尾节点需要一个 prev
指针,并且 put
的节点可能在链表的任何位置,在不借助其他结构存储节点位置的情况(如用数组),那么每个节点都需要有一个 prev
指针,用来修改前后节点的指针,至此,能确定这道题需要用到双向链表
。
但细节比较多,尤其是指针的操作,很容易出错,最终代码还是参考了题解写出来的。
/**
* @param {number} capacity
*/
var LRUCache = function (capacity) {
this.capacity = capacity;
this.head = new ListNode();
this.last = new ListNode();
this.head.next = this.last;
this.last.prev = this.head;
this.useSpace = 0
this.map = {};
};
LRUCache.prototype.isFull = function () {
return this.useSpace == this.capacity;
};
/**
* @param {number} key
* @return {number}
*/
LRUCache.prototype.get = function (key) {
let node = this.map[key];
if (node) {
this.appendHead(this.removeNode(node));
return node.val;
}
return -1;
};
LRUCache.prototype.removeNode = function (node) {
node.prev.next = node.next;
node.next.prev = node.prev;
node.prev = null;
node.next = null;
return node;
};
LRUCache.prototype.appendHead = function (node) {
node.prev = this.head;
node.next = this.head.next;
this.head.next = node;
node.next.prev = node;
};
/**
* @param {number} key
* @param {number} value
* @return {void}
*/
LRUCache.prototype.put = function (key, value) {
if (key in this.map) {
let node = this.map[key]
this.appendHead(this.removeNode(node));
this.map[key] = node
node.val = value;
} else {
if (this.isFull()) {
const node = this.last.prev;
delete this.map[node.key];
const newNode = this.removeNode(node)
newNode.val = value
newNode.key = key
this.appendHead(newNode);
this.map[key] = newNode
} else {
let node = new ListNode(value);
node.key = key
this.appendHead(node);
this.map[key] = node
this.useSpace ++
}
}
};
时间复杂度:O(1)
空间复杂度:O(N)
146. LRU 缓存机制
入选理由
暂无
题目地址
https://leetcode-cn.com/problems/lru-cache/
前置知识
暂无
题目描述