• LeetCode 146. LRU 缓存


    146. LRU 缓存

    请你设计并实现一个满足  LRU (最近最少使用) 缓存 约束的数据结构。

    实现 LRUCache 类:

    • LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
    • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
    • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

    函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

    示例:

    输入
    ["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
    [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
    输出
    [null, null, null, 1, null, -1, null, -1, 3, 4]
    
    解释
    LRUCache lRUCache = new LRUCache(2);
    lRUCache.put(1, 1); // 缓存是 {1=1}
    lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
    lRUCache.get(1);    // 返回 1
    lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
    lRUCache.get(2);    // 返回 -1 (未找到)
    lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
    lRUCache.get(1);    // 返回 -1 (未找到)
    lRUCache.get(3);    // 返回 3
    lRUCache.get(4);    // 返回 4
    

    提示:

    • 1 <= capacity <= 3000
    • 0 <= key <= 10000
    • 0 <= value <= 105
    • 最多调用 2 * 105 次 get 和 put

    思路:

    • 参考:力扣
    • 更多lru相关知识可学习大佬文章:其实吧,LRU也就那么回事。
    • 借助hash链表(hashmap + 双向链表)这一组合的数据结构
    • Put() 操作中:
      • 若key已存在:更新值并将该节点移动到最前
      • 若key不存在:判断链表容量是否已满,若满则移除链表尾元素后再加入新节点到链表,未满则直接加入
    • Get() 操作中:
      • 若已存在:直接返回元素的val值,并把该元素移除并重新加入到首位置上去(链表头部表示近期访问过的节点元素)
      • 若不存在:返回-1
    • 注意:若元素被get()或put()操作都算被访问过,需要把该元素移除并重新加入到首位置上去
    • 其中 AddNode() 方法将某节点元素重新加入到首位置,其中双向链表的指针操作较多,可参照图示:

    时间复杂度:get()、put()方法,由于是hash链表的数据结构,通过空间换时间的方式,均为O(1)

    空间复杂度:get()、put()方法,由于是hash链表的数据结构,通过空间换时间的方式,均为O(1)

    1. type LinkNode struct {
    2. key, val int
    3. pre, next *LinkNode
    4. }
    5. // 其中head和tail都为虚拟节点,不存储实际数据,仅用于标记双向链表首尾位置
    6. type LRUCache struct {
    7. m map[int]*LinkNode
    8. cap int
    9. head, tail *LinkNode
    10. }
    11. func Constructor(capacity int) LRUCache {
    12. head := &LinkNode{0, 0, nil, nil}
    13. tail := &LinkNode{0, 0, nil, nil}
    14. head.next = tail
    15. tail.pre = head
    16. return LRUCache{make(map[int]*LinkNode), capacity, head, tail}
    17. }
    18. func (this *LRUCache) Get(key int) int {
    19. cache := this.m
    20. if v, exist := cache[key]; exist {
    21. this.MoveToHead(v)
    22. return v.val
    23. } else {
    24. return -1
    25. }
    26. }
    27. func (this *LRUCache) Put(key int, value int) {
    28. tail := this.tail
    29. cache := this.m
    30. if v, exist := cache[key]; exist {
    31. // 若存在:更新值并将该节点移动到最前
    32. v.val = value
    33. this.MoveToHead(v)
    34. } else {
    35. // 若不存在:判断链表容量是否已满,若满则移除链表尾元素后再加入新节点到链表,未满则直接加入
    36. v := &LinkNode{key, value, nil, nil}
    37. // 超出容量cap,删除最后元素
    38. if len(cache) == this.cap {
    39. delete(cache, tail.pre.key)
    40. // tail是虚拟的尾节点,移除 tail.pre 等同于下面两句
    41. this.RemoveNode(tail.pre)
    42. // tail.pre.pre.next = tail
    43. // tail.pre = tail.pre.pre
    44. }
    45. this.AddNode(v)
    46. cache[key] = v
    47. }
    48. }
    49. // 以下为内部公共函数
    50. // 访问过某节点元素,则将该节点元素从当前位置移除并重新加入到首位置
    51. func (this *LRUCache) MoveToHead(node *LinkNode) {
    52. this.RemoveNode(node)
    53. this.AddNode(node)
    54. }
    55. // 移除某节点元素
    56. func (this *LRUCache) RemoveNode(node *LinkNode) {
    57. node.pre.next = node.next
    58. node.next.pre = node.pre
    59. }
    60. // 将某节点元素重新加入到首位置
    61. func (this *LRUCache) AddNode(node *LinkNode) {
    62. head := this.head
    63. node.next = head.next
    64. head.next.pre = node
    65. node.pre = head
    66. head.next = node
    67. }

  • 相关阅读:
    基于PHP的玩偶玩具商城网站设计
    判断图中是否有环
    前端笔记(6) Vue3 dialog弹窗 父子组件之间传值及方法调用
    SpingBoot整合Quartz框架实现动态定时任务(支持实时增删改查任务)
    【JAVA并发】AQS原理详解
    Doris的分区表和分桶表
    堆叠、集群技术
    RabbitMQ高级特性
    【华为上机真题 2022】流水线
    搭建帮助中心系统的关键注意事项
  • 原文地址:https://blog.csdn.net/qq_37102984/article/details/126146228