1. 程式人生 > >LeetCode 146 Hard 實現LRU演算法 2種解法 Python

LeetCode 146 Hard 實現LRU演算法 2種解法 Python

"""
    My Method
    演算法:雙向連結串列+字典
    思路:
        首先一定要回顧一下LRU演算法,其實就是一個優先佇列,最近使用過的在隊尾,時間久的在隊頭,該佇列
    是有長度限制的,超出長度後最久未訪問的元素出佇列,類似於一種先進先出。然後訪問元素的時候,要將
    該元素設為最近訪問過的元素
        我這裡就設隊頭代表最近最久未訪問,佇列尾代表最近訪問的元素
        很容易想到用線性表去做,但是也很容易發現,用線性表的話在get調整元素位置的時候會挪動大量的元素,
    導致時間效率很低,這個時候可以很容易想到用連結串列結構去做,用連結串列的話將一個元素拆下來放到佇列尾是O1的
    時間複雜度,所以只要設定一個佇列頭指標head和一個尾指標tail就OK了,因為拆卸連結串列需要知道節點的前驅,
    所以用雙向連結串列來記錄節點的情況,同時需要設立一個字典dict記錄節點的記憶體地址,達到get時快速訪問的目的。
        因此可以將LRU描述為用含head和tail指標的佇列進行put,get操作
            get時:
            key在dict中時,將節點remove掉,再append回佇列,就完成了node的位置調整
            put時:
            要注意同key的node更新,也是先將節點remove掉,然後再append進來,只不過append的時候要改一下node.val
            不同key的話:
                先要考慮佇列是否滿,佇列長度滿則彈出隊首,否則的話計數加一,然後都要append新的node
        所以要建立啊remove和append的輔助函式進行操作
            remove拿到node的前驅就可以正常刪了,但是要注意node可能是tail,所以要將self.tail更新為前一個位置!
            append的話就比較直觀了,在self.tail後面追加這個node即可
        再一個就是不要忘記刪除節點和新增節點的時候要更新dict
    複雜度分析:
        時間:O1
        空間:ON

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


class LRUCache:

    def __init__(self, capacity):
        """
        :type capacity: int
        """
        self.head = ListNode(0, 0)
        self.tail = self.head
        self.node_dict = {}
        self.capacity = capacity

    def get(self, key):
        """
        :type key: int
        :rtype: int
        """
        if key in self.node_dict:
            node = self.node_dict[key]
            node = self.del_node(node)
            self.append(node)
            return node.val
        else:
            return -1

    def put(self, key, value):
        """
        :type key: int
        :type value: int
        :rtype: void
        """
        if key in self.node_dict:
            node = self.node_dict[key]
            node = self.del_node(node)
            node.val = value
            self.append(node)
            return
        if len(self.node_dict)== self.capacity:
            self.node_dict.pop(self.head.next.key)
            self.del_node(self.head.next)
        node = ListNode(key, value)
        self.append(node)
        self.node_dict[key] = node

    def del_node(self, node):
        if node == self.tail:
            self.tail = node.pre
        pre = node.pre
        next = node.next
        pre.next = node.next
        node.pre = None
        node.next = None
        if next != None:
            next.pre = pre
        return node

    def append(self, node):
        self.tail.next = node
        node.next = None
        node.pre = self.tail
        self.tail = self.tail.next
"""
----------------------------------------------------------------
    Disscussion Method 1
    
        其實我也是看了Disscution後棄用了單鏈表,不過之前我的做法中用單鏈表+dict儲存pre和雙向連結串列也差不多,還不如
    直接雙向連結串列更加直接。
        下面的這個方法是設立了兩個啞節點headdummy和taildummy,而我的方法是head是dummy,tail是指向
        實在的元素的,他這種寫法也是比較簡潔的
        dummyhead-->1-->2-->3-->4-->taildummy
        而我的是:
        dummyhead-->1-->2-->3-->4 <--tail
                               
    其實他這種兩個dummy的可能看起來會更方便一些,我這種的話tail每次要棟,而dummytail的話就像dummyhead一樣
    dummyhead.next是真實的head
    dummytail.prev是真實的tail
"""
class Node(object):

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


class LRUCache1(object):

    def __init__(self, capacity):
        self.dic = {}
        self.capacity = capacity
        self.dummy_head = Node(0, 0)
        self.dummy_tail = Node(0, 0)
        self.dummy_head.next = self.dummy_tail
        self.dummy_tail.prev = self.dummy_head

    def get(self, key):
        if key not in self.dic:
            return -1
        node = self.dic[key]
        self.remove(node)
        self.append(node)
        return node.val

    def put(self, key, value):
        if key in self.dic:
            self.remove(self.dic[key])
        node = Node(key, value)
        self.append(node)
        self.dic[key] = node

        if len(self.dic) > self.capacity:
            head = self.dummy_head.next
            self.remove(head)
            del self.dic[head.key]

    def append(self, node):
        tail = self.dummy_tail.prev
        tail.next = node
        node.prev = tail
        self.dummy_tail.prev = node
        node.next = self.dummy_tail

    def remove(self, node):
        prev = node.prev
        next = node.next
        prev.next = next
        next.prev = prev

"""
----------------------------------------------------------------------------

    Disscussion Method 2
    
    Do as they say
    演算法:小頂堆
    思路:
            用優先佇列,or 堆來保持LRU的狀態,就像do as they say一樣,用time來儲存時間戳,利用最小堆通過時間戳
        的大小來儲存當前應當替換的頁面key
            這樣思路就比較直觀了
            用dic去儲存時間戳和key,value,dict[key] = (time,value)
            所以當get的時候,就判斷key in dict or not,在的話就返回value並更新dict中儲存的key的時間戳
            put的時候,也是先判斷key如果在dic中,就更新dict[key]中的時間戳為最新的
            不在put中的話同樣先判斷len(dic) > capacity or not,
                要做一些【清理工作】
                    具體來說就是檢查有沒有節點的時間戳在後面get 或者put的時候被更新了,因為最小堆不像連結串列一樣
                可以直接找到節點的位置並且拆卸下來,所以這種思路就是曲線去找,如果當前應該彈出的堆頂元素髮現
                其實後面被更新過了,堆頂的時間戳time和dict[key]中儲存的時間戳不一樣,那麼久更新堆頂元素,
                一直更新直到找到一個元素確實是最小堆堆頂該彈出,就把他彈出就好了,heapq.pop,並且記得要del dict[key]
            然後push新的時間戳的key,value就好了
    複雜度:
        時間:OK,K是佇列長度,肯定不是O1,get是O1,put不是O1,因為要做清理工作,但是至多對佇列內所有的K個元素每個都更新
        一次,所以OK,效率也還是挺高的
"""

from time import time
import heapq

class LRUCache2:

    def __init__(self, capacity):
        """
        :type capacity: int
        """
        self.cap = capacity
        self.hp = []  # (pre,vale)
        self.dic = {}  # (now, value)

    def get(self, key):
        """
        :type key: int
        :rtype: int
        """
        if key not in self.dic:  # never exits before
            return -1
        pre, val = self.dic[key]
        new = time()
        self.dic[key] = (new, val)
        return val

    def put(self, key, value):
        """
        :type key: int
        :type value: int
        :rtype: void
        """
        now = time()
        if key in self.dic:
            self.dic[key] = (now, value)
            return
        if len(self.hp) >= self.cap:
            self.clean()
        self.dic[key] = (now, value)
        heapq.heappush(self.hp, (now, key))

    def clean(self):
        while (True):
            (pre, key) = self.hp[0]
            (new, value) = self.dic[key]
            if new > pre:
                heapq.heapreplace(self.hp, (new, key))
            else:
                heapq.heappop(self.hp)
                del self.dic[key]
                return