146. LRU緩存機(jī)制
運(yùn)用你所掌握的數(shù)據(jù)結(jié)構(gòu),設(shè)計(jì)和實(shí)現(xiàn)一個(gè) LRU (最近最少使用) 緩存機(jī)制。它應(yīng)該支持以下操作: 獲取數(shù)據(jù) get 和 寫入數(shù)據(jù) put 。
獲取數(shù)據(jù) get(key) - 如果密鑰 (key) 存在于緩存中,則獲取密鑰的值(總是正數(shù))健无,否則返回 -1。
寫入數(shù)據(jù) put(key, value) - 如果密鑰不存在液斜,則寫入其數(shù)據(jù)值累贤。當(dāng)緩存容量達(dá)到上限時(shí),它應(yīng)該在寫入新數(shù)據(jù)之前刪除最近最少使用的數(shù)據(jù)值少漆,從而為新的數(shù)據(jù)值留出空間臼膏。
進(jìn)階:
你是否可以在 O(1) 時(shí)間復(fù)雜度內(nèi)完成這兩種操作?
示例:
LRUCache cache = new LRUCache( 2 /* 緩存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 該操作會(huì)使得密鑰 2 作廢
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 該操作會(huì)使得密鑰 1 作廢
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
解題思路
- LRU是Least Recently Used的縮寫,即"最近最少使用",也就是說,LRU緩存把最近最少使用的數(shù)據(jù)移除,讓給最新讀取的數(shù)據(jù)
- 往往最常讀取的,也是讀取次數(shù)最多的
- 操作系統(tǒng)等最常使用的緩存策略,即LRU
- 需要在O(1)時(shí)間復(fù)雜度實(shí)現(xiàn)put操作,就需要使用雙向鏈表,方便移動(dòng)節(jié)點(diǎn)(單鏈表無法達(dá)到O(1))
- O(1)實(shí)現(xiàn)get查詢操作,就需要使用map存key-node鍵值對(duì),實(shí)現(xiàn)快速查詢
- 具體詳見代碼注釋
代碼實(shí)現(xiàn)
// doublyLinkedNode defines a node for double-linked-list
type doublyLinkedNode struct {
prev, next *doublyLinkedNode
key, val int
}
// LRUCache defines a object for cache
type LRUCache struct {
len, cap int
first, last *doublyLinkedNode //head,tail
nodes map[int]*doublyLinkedNode //hashtable,find node in O(1)
}
// Constructor creates a cache object
func Constructor(capacity int) LRUCache {
return LRUCache{
len: 0,
cap: capacity,
first: nil,
last: nil,
nodes: make(map[int]*doublyLinkedNode, capacity),
}
}
// Get returns value by key
func (l *LRUCache) Get(key int) int {
if node, ok := l.nodes[key]; ok {
//key exist
// move the node to the head of double-linked-list
l.moveToFirst(node)
return node.val
}
//key not exist,return -1
return -1
}
// Put puts key-value pair into LRUCache
func (l *LRUCache) Put(key int, value int) {
if node, ok := l.nodes[key]; ok {
//update value of old node
node.val = value
// move the node to the head of double-linked-list
l.moveToFirst(node)
} else {
if l.len == l.cap {
delete(l.nodes, l.last.key)
l.removeLast()
} else {
l.len++
}
node := &doublyLinkedNode{
prev: nil,
next: nil,
key: key,
val: value,
}
l.nodes[key] = node
l.insertToFirst(node)
}
}
func (l *LRUCache) removeLast() {
if l.last.prev != nil {
//雙向鏈表長(zhǎng)度>1
l.last.prev.next = nil
} else {
//雙向鏈表長(zhǎng)度 == 1,first == last
l.first = nil
}
l.last = l.last.prev
}
func (l *LRUCache) moveToFirst(node *doublyLinkedNode) {
switch node {
case l.first:
return
case l.last:
l.removeLast()
default:
//在雙向鏈中示损,刪除 node 節(jié)點(diǎn)
node.prev.next = node.next
node.next.prev = node.prev
}
// 策略是
// 如果是移動(dòng)node
// 先刪除,再插入
l.insertToFirst(node)
}
func (l *LRUCache) insertToFirst(node *doublyLinkedNode) {
if l.last == nil {
//空的雙向鏈表
l.last = node
} else {
node.next = l.first
l.first.prev = node
}
l.first = node
}
GitHub
- 源碼在這里
- 項(xiàng)目中會(huì)提供各種數(shù)據(jù)結(jié)構(gòu)及算法的Golang實(shí)現(xiàn),以及 LeetCode 解法