LeetCode LRUキャッシングメカニズム(146の質問)

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;
        }
    }
}

おすすめ

転載: blog.csdn.net/qq_41512783/article/details/111054414