LeetCode146. LRU缓存机制(哈希+双向链表)

1、题目描述

https://leetcode-cn.com/problems/lru-cache/

设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。

  • 获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
  • 写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。

当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

LRU 缓存淘汰算法就是一种常用策略。LRU 的全称是 Least Recently Used,最近使用过的数据应该是是「有用的」,很久都没用过的数据应该是无用的,内存满了就优先删那些很久没用过的数据。

举例,安卓手机软件的后台运行,比如先后打开了「设置」「手机管家」「日历」等应用。

# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)
cache = LRUCache(2)

cache.put(1, 1)  # put(key, value)
cache.put(2, 2)
# get(key), 获取关键字的值(总是正数),否则返回 -1
print(cache.get(1))  # 返回  1
cache.put(3, 3)  # 该操作会使得密钥 2 作废
print(cache.get(2))  # 返回 -1 (未找到)
cache.put(4, 4)  # 该操作会使得密钥 1 作废
print(cache.get(1))  # 返回 -1 (未找到)
print(cache.get(3))  # 返回  3
print(cache.get(4))  # 返回  4
class LRUCache:
    def __init__(self, capacity: int):

    def get(self, key: int) -> int:

    def put(self, key: int, value: int) -> None:

2、代码详解(哈希+双向链表)

字典存k-v:用字典来存储 key-value 结构,这样对于查找操作时间复杂度就是 O(1)。

双向链表:但是因为字典本身是无序的,还需要一个类似于队列的结构来记录访问的先后顺序。这个队列需要支持如下几种操作

  • 在末尾加入一项
  • 去除最前端一项
  • 将队列中某一项移到队首
class ListNode:
    def __init__(self, key=None, value=None):
        self.key = key
        self.value = value
        self.prev = None
        self.next = None


class LRUCache:
    # init函数
    def __init__(self, capacity: int):
        self.capacity = capacity
        self.hashmap = {}  # 用字典来存储 key-value,查找操作O(1)

        # 新建两个节点 head 和 tail
        self.head = ListNode()
        self.tail = ListNode()
        # 初始化链表为 head <-> tail
        self.head.next = self.tail
        self.tail.prev = self.head

    # 因为get与put操作都可能需要将双向链表中的某个节点移到头部(变成最新访问的)
    # 子方法:将该节点移到链表头部
    def move_node_to_header(self, key):
        # (1)先将哈希表key指向的节点拎出来,为了简洁起名node
        #      hashmap[key]                               hashmap[key]
        #           |                                          |
        #           V              -->                         V
        # prev <-> node <-> next         pre <-> next   ...   node
        node = self.hashmap[key]
        node.prev.next = node.next  # prev -> next
        node.next.prev = node.prev  # prev <- next
        # (2)之后将node插入到头部节点前
        #                   hashmap[key]                     hashmap[key]
        #                       |                                 |
        #                       V        -->                      V
        # header <-> next  ... node                   header <-> node <-> next
        node.prev = self.head       # head <- node
        node.next = self.head.next  # node -> next
        self.head.next.prev = node  # node <- next
        self.head.next = node       # head -> node
    # 子方法:在头部插入节点new
    def add_node_to_header(self, key, value):
        new = ListNode(key, value)
        self.hashmap[key] = new
        # head <-> new <-> head.next
        new.prev = self.head  # head <- new
        new.next = self.head.next  # new -> head.next
        self.head.next.prev = new  # new <- head.next
        self.head.next = new  # head -> new
    # 子方法:删除尾结点(若cache容量已满)
    def pop_tail(self):
        last_node = self.tail.prev
        # 去掉链表尾部的节点在哈希表的对应项
        self.hashmap.pop(last_node.key)
        # 去掉最久没有被访问过的节点,即尾部Tail之前的一个节点
        # last_node.prev - last_node - tail
        last_node.prev.next = self.tail
        self.tail.prev = last_node.prev
        # last_node.prev <-> tail
        return last_node

    # get,O(1)
    def get(self, key: int) -> int:
        if key in self.hashmap:
            # 如果已经在链表中了就把它移到头部(变成最新访问的)
            self.move_node_to_header(key)
        res = self.hashmap.get(key, -1)
        if res == -1:
            return res
        else:
            return res.value
    # put,O(1)
    def put(self, key: int, value: int) -> None:
        if key in self.hashmap:
            # 如果key本身已经在哈希表中了就不需要在链表中加入新的节点
            # 但是需要更新字典该值对应节点的value
            self.hashmap[key].value = value
            # 之后将该节点移到链表头部
            self.move_node_to_header(key)
        else:
            if len(self.hashmap) >= self.capacity:
                # 若cache容量已满,删除cache中最不常用的节点
                self.pop_tail()
            self.add_node_to_header(key, value)

LeetCode146. LRU缓存机制(哈希+双向链表)_第1张图片

https://leetcode-cn.com/problems/lru-cache/solution/shu-ju-jie-gou-fen-xi-python-ha-xi-shuang-xiang-li/

https://leetcode-cn.com/problems/lru-cache/solution/lru-ce-lue-xiang-jie-he-shi-xian-by-labuladong/

你可能感兴趣的:(LinkedList,LRU)