LeetCode146 動手實現LRU演算法

TomorrowWu發表於2018-10-22

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是Least Recently Used的縮寫,即"最近最少使用",也就是說,LRU快取把最近最少使用的資料移除,讓給最新讀取的資料
  • 往往最常讀取的,也是讀取次數最多的
  • 作業系統等最常使用的快取策略,即LRU
  • 需要在O(1)時間複雜度實現put操作,就需要使用雙向連結串列,方便移動節點(單連結串列無法達到O(1))
  • O(1)實現get查詢操作,就需要使用map存key-node鍵值對,實現快速查詢
  • 具體詳見程式碼註釋

程式碼實現

// 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 {
		//雙向連結串列長度>1
		l.last.prev.next = nil
	} else {
		//雙向連結串列長度 == 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 節點
		node.prev.next = node.next
		node.next.prev = node.prev
	}
	// 策略是
	// 如果是移動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

  • 原始碼在這裡
  • 專案中會提供各種資料結構及演算法的Golang實現,以及 LeetCode 解法

相關文章