LeetCode LRUキャッシングメカニズム(146の質問)
@author:Jingdai
@date:2020.12.11
トピックの説明(146の質問)
知っているデータ構造を使用して、LRU(最近使用されていない)キャッシュメカニズムを設計および実装します。
LRUCache
カテゴリを実現する:
LRUCache(int capacity)
正の整数capacity
初期化LRUキャッシュとしての容量。int get(int key)
キーワードkey
がキャッシュに存在する場合は、キーの値が返されます-1
。存在しない場合は、が返されます。void put(int key, int value)
キーワードがすでに存在する場合は、そのデータ値を変更します。キーワードが存在しない場合は、「keyword-value」のセットを挿入します。キャッシュ容量が上限に達すると、新しいデータを書き込む前に最も古い未使用のデータ値を削除して、新しいデータ値のためのスペースを確保する必要があります。
アイデアとコード
このトピックでは、各操作の時間計算量が必要であり、O(1)
使用するデータ構造を慎重に検討する必要があります。
まずkey
、リストまたは配列を直接使用する場合、key
必要性を探しますがO(n)
、明らかにそうではありません。O(1)
値を見つけるための基本はハッシュテーブルなので、必ずハッシュテーブルを保存してくださいkey
。
次は値です。HashMapのように直接保存すると、どれが最も未使用のデータであるかがわかりません。したがって、値データを順序付けする必要があり、配列とリンクリストを使用できます。どれが最も長く使用されていないデータであるかを知る必要があるため、各操作でデータを移動する必要があり、データを移動する配列の複雑さはO(n)
、リンクリストのみが残ります。単一リンクリストの場合、ノードを削除および移動すると、その先行ノードを見つける必要があり、単一リンクリストをトラバースして先行ノードを取得する必要があります。複雑になるO(n)
ため、二重リンクリストを使用するのが自然です。
要約すると、基本的なデータ構造は、ハッシュテーブルと二重リンクリストです。
次に、実現のアイデアを見てください。
-
リンクリストの操作では、ヘッドノードとテールノードを特別に処理しないために、値を格納しないヘッドノードとテールノードが一般的に追加されます。これは、コードの記述に便利で、特別な必要はありません。頭と尾のノードの判断。
-
Get操作:ここでは、最後に使用されたノードがヘッドノードの後に配置され、最も長く使用されていないノードがテールノードの前に配置されます。get操作に値がない場合は、直接-1を返します。値がある場合は、最初に対応するノードをヘッドノードの後のノードに移動してから、その値を返します。
-
put操作:
key
既存の場合、対応するノードの値によってモバイルノードと対応するノードがヘッドに変更されます。key
容量が上限値に達していない場合、および上限値に達していない場合は、新しいノードとヘッドノード。key
容量が上限値に達していない場合、ノードを削除する前に、この新しいノードとヘッドノードおよびテールノードに。
実装プロセスで問題が見つかります。最近使用されていないノードを削除する必要がある場合は、テールノードの二重リンクリストを削除するだけで済みますが、ハッシュテーブルも削除key
すると問題が発生し、key
何であるかわかりません。値を格納する二重リンクリストノードは、値を格納するだけでなく、値も格納する必要があるkey
ため、ノードが削除されたときに、ハッシュテーブルで削除できますkey
。
最終的なコードは次のとおりです。
class DoubleLinkedNode {
int key;
int value;
DoubleLinkedNode prev;
DoubleLinkedNode next;
DoubleLinkedNode(){
}
DoubleLinkedNode(int key, int value) {
this.key = key;
this.value = value;
}
}
private DoubleLinkedNode head;
private DoubleLinkedNode tail;
private int size;
private int capacity;
private HashMap<Integer, DoubleLinkedNode> cache;
public LRUCache(int capacity) {
head = new DoubleLinkedNode();
tail = new DoubleLinkedNode();
cache = new HashMap<>();
head.next = tail;
tail.prev = head;
this.capacity = capacity;
this.size = 0;
}
// head recently used
public int get(int key) {
if (cache.containsKey(key)) {
DoubleLinkedNode node = cache.get(key);
// take out node
node.prev.next = node.next;
node.next.prev = node.prev;
// put it behind the head
node.next = head.next;
node.prev = head;
head.next = node;
node.next.prev = node;
return node.value;
}
return -1;
}
public void put(int key, int value) {
DoubleLinkedNode node = cache.get(key);
if (node != null) {
node.value = value;
// take out node
node.prev.next = node.next;
node.next.prev = node.prev;
// put it behind the head
node.next = head.next;
node.prev = head;
head.next = node;
node.next.prev = node;
} else {
if (this.size < capacity) {
node = new DoubleLinkedNode(key, value);
cache.put(key, node);
// put it behind the head
node.next = head.next;
node.next.prev = node;
head.next = node;
node.prev = head;
size ++;
} else if (this.size == capacity && capacity != 0){
// delete the last
cache.remove(tail.prev.key);
tail.prev.prev.next = tail;
tail.prev = tail.prev.prev;
node = new DoubleLinkedNode(key, value);
cache.put(key, node);
// put it behind the head
node.next = head.next;
node.next.prev = node;
head.next = node;
node.prev = head;
}
}
}