🐙

NeetCode 150 [Linked List]medium:LRU Cache

に公開

NeetCodeのSolutionを書いていく

LRU Cache

問題

Least Recently Used Cacheを実装しましょう。
以下の操作をサポートする必要があります。

  • LRUCache(int capacity) でLRUのキャッシュサイズを初期化します
  • int get(int key) でkeyがある場合はその値を、ない場合は-1を返します
  • void put(int key, int value)keyがある場合はそのkeyvalueを更新します。存在しない場合はkey-valueのペアをキャッシュに追加します。
  • 新しいペア導入時にキャッシュサイズを超える場合、最も最後に使ったキーのペアを削除します
  • getあるいはputが呼び出されたらそのkeyは使われたとします

制約

計算量:getputは平均でO(1)
メモリ:O(n)

Input:
["LRUCache", [2], "put", [1, 10], "get", [1], "put", [2, 20], "put", [3, 30], "get", [2], "get", [1]]

Output:
[null, null, 10, null, null, 20, -1]

Explanation:
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 10); // cache: {1=10}
lRUCache.get(1); // return 10
lRUCache.put(2, 20); // cache: {1=10, 2=20}
lRUCache.put(3, 30); // cache: {2=20, 3=30}, key=1 was evicted
lRUCache.get(2); // returns 20
lRUCache.get(1); // return -1 (not found)

メモ

keyはユニークなのでDictが使える。
DuctだとO(1)でget,putできる。
しかしDuctだと順番が管理しづらいか?
Listにkeyを保持する形だと、keyの削除でO(n)になってしまうのでだめ。

以下方針だとどうだろうか。

  • key-valueはDictで管理する
  • keyを管理する双方向リスト(cache list)を実装
  • putされたらkeyをcache listの末尾に追加
  • putしたときにキャッシュサイズを超えたら最初のデータを削除
  • getされたらkeyをcache listの末尾に移動

キャッシュのheadとtailを持っておく
メモリがO(n)使えるのでkeyをキーに、LinkedListのnodeを保持しておく

class ListNode:
    def __init__(self, key=0, val=0):
        self.key = key
        self.val = val
        self.prev = None
        self.next = None


class LRUCache:
    def __init__(self, capacity: int):
        self.capacity = capacity
        self.cache: dict[int, ListNode] = {}

        self.head = ListNode()  # ダミーhead
        self.tail = ListNode()  # ダミーtail
        self.head.next = self.tail
        self.tail.prev = self.head

    def _remove(self, node):
        # ノードをリストから外す
        prev, nxt = node.prev, node.next
        prev.next = nxt
        nxt.prev = prev

    def _add(self, node):
        # ノードを末尾に追加(tailの前)
        prev = self.tail.prev
        prev.next = node
        node.prev = prev
        node.next = self.tail
        self.tail.prev = node

    def get(self, key: int) -> int:
        if key in self.cache:
            node = self.cache[key]
            self._remove(node)
            self._add(node)
            return node.val
        return -1

    def put(self, key: int, value: int) -> None:
        if key in self.cache:
            self._remove(self.cache[key])

        node = ListNode(key, value)
        self._add(node)
        self.cache[key] = node

        if len(self.cache) > self.capacity:
            # head.next が一番古いノード
            lru = self.head.next
            self._remove(lru)
            del self.cache[lru.key]

Discussion