• LeetCode 460. LFU 缓存


    原题链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

    题目描述

    请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。

    实现 LFUCache 类:

    • LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象
    • int get(int key) - 如果键 key 存在于缓存中,则获取键的值,否则返回 -1
    • void put(int key, int value) - 如果键 key 已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量 capacity 时,则应该在插入新项之前,移除最不经常使用的项。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最久未使用 的键。

    为了确定最不常使用的键,可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。

    当一个键首次插入到缓存中时,它的使用计数器被设置为 1 (由于 put 操作)。对缓存中的键执行 getput 操作,使用计数器的值将会递增。

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

    样例1:

    输入

    ["LFUCache", "put", "put", "get", "put", "get", "get", "put", "get", "get", "get"]
    [[2], [1, 1], [2, 2], [1], [3, 3], [2], [3], [4, 4], [1], [3], [4]]

    输出

    [null, null, null, 1, null, -1, 3, null, -1, 3, 4]

     // cnt(x) = 键 x 的使用计数
    // cache=[] 将显示最后一次使用的顺序(最左边的元素是最近的)
    LFUCache lfu = new LFUCache(2);
    lfu.put(1, 1);   // cache=[1,_], cnt(1)=1
    lfu.put(2, 2);   // cache=[2,1], cnt(2)=1, cnt(1)=1
    lfu.get(1);      // 返回 1
                     // cache=[1,2], cnt(2)=1, cnt(1)=2
    lfu.put(3, 3);   // 去除键 2 ,因为 cnt(2)=1 ,使用计数最小
                     // cache=[3,1], cnt(3)=1, cnt(1)=2
    lfu.get(2);      // 返回 -1(未找到)
    lfu.get(3);      // 返回 3
                     // cache=[3,1], cnt(3)=2, cnt(1)=2
    lfu.put(4, 4);   // 去除键 1 ,1 和 3 的 cnt 相同,但 1 最久未使用
                     // cache=[4,3], cnt(4)=1, cnt(3)=2
    lfu.get(1);      // 返回 -1(未找到)
    lfu.get(3);      // 返回 3
                     // cache=[3,4], cnt(4)=1, cnt(3)=3
    lfu.get(4);      // 返回 4
                     // cache=[3,4], cnt(4)=2, cnt(3)=3

    Tag

    双向链表 set 哈希映射

    题目分析

    这题是LeetCode 146. LRU 缓存-CSDN博客的升级版,加入了使用次数判断,如果只用一个双向链表的话无法满足需求,需要根据使用频率cnt来维护多个双向链表。

    思路

    数据结构:

    1.链表节点

    记录value值,有指向前节点和后节点的指针

    2.双向链表

    有链表头尾节点,借助头结点可以将新加入的节点O(1)复杂度加入队首,借助尾节点可以O(1)复杂度去除最久未使用的节点(即尾节点prev指向的节点)。

    用哈希表unordered_map mp维护关键字到链表节点地址的映射,确保可以在O(1)复杂度时间根据关键字找到节点

    3.LFUCache类

    哈希表list_ptr维护多条双向链表,根据关键字使用频率找到所存储的链表位置,并进行相应操作

    哈希表cnt记录关键字出现的次数

    红黑树min_cnt记录当前存在的频率最低的关键字有哪些(即找到cnt最低的那条双向链表)

    实现方法

    1.get查询

    找到了将关键字所在节点node从当前频率的链表中删掉,放到当前频率+1的链表首部,同时更新cnt修改关键字出现的次数,若当前频率+1还有链表需要申请并用list_ptr记录

    2.put操作

    修改频率和get一样,不同的是需要判断是否找过缓存,如果超过根据min_cnt先找到频率最低的那条链表,再删掉链表的尾节点,同时注意更新min_cnt、cnt和list_ptr

    3.注意

    删除节点后需要判断链表是否为空,若为空则需要删除这条链表,并且删掉min_cnt中的记录和list_ptr中记录当前链表的地址

    C++代码

    1. class node{
    2. public:
    3. node *prev,*next;
    4. int key;
    5. int value;
    6. node(node *prev,node *next,int key,int value)
    7. {
    8. this->key = key;
    9. this->value = value;
    10. this->prev = prev;
    11. this->next = next;
    12. }
    13. };
    14. class DoublyLinkedList{
    15. public:
    16. node *head,*tail;
    17. unordered_map<int,node*> mp;//key:关键字 value:存储链表中某个节点地址
    18. DoublyLinkedList()
    19. {
    20. head = new node(nullptr,nullptr,0,0);
    21. tail = new node(head,nullptr,0,0);
    22. head->next = tail;
    23. }
    24. //将node移动到队首
    25. void adjust(node* temp)
    26. {
    27. temp->next = head->next;
    28. temp->prev = head;
    29. head->next = temp;
    30. temp->next->prev = temp;
    31. }
    32. //删除节点
    33. void remove(node *move){
    34. move->prev->next = move->next;
    35. move->next->prev = move->prev;
    36. }
    37. //删除尾节点
    38. int remove_tail(){
    39. node* del = tail->prev;
    40. int key = del->key;
    41. tail->prev = del->prev;
    42. del->prev->next = tail;
    43. mp.erase(key);
    44. return key;
    45. }
    46. };
    47. class LFUCache {
    48. public:
    49. int capacity;
    50. int now;
    51. unordered_map<int,DoublyLinkedList*> list_ptr;//key:使用次数 value:双向链表地址
    52. unordered_map<int,int>cnt;//key:关键字 value:出现次数
    53. set<int>min_cnt;//查找出现次数最少的
    54. LFUCache(int capacity) {
    55. this->capacity = capacity;
    56. now = 0;
    57. }
    58. int get(int key) {
    59. if(cnt.count(key)){
    60. int count = cnt[key];//关键字查询前出现次数
    61. cnt[key]++;//查询后访问次数+1
    62. DoublyLinkedList *list = list_ptr[count];
    63. //cout<<"size:"<
    64. int value = list->mp[key]->value;//查询到的值
    65. //从count中删除尾节点
    66. node* move = list->mp[key];
    67. list->remove(move);
    68. if(list->head->next == list->tail){//删空了
    69. list_ptr.erase(count);
    70. min_cnt.erase(count);
    71. }
    72. //加入count+1中的队首
    73. if(list_ptr.count(count+1)){
    74. DoublyLinkedList *newlist = list_ptr[count+1];
    75. newlist->adjust(move);
    76. newlist->mp[key] = move;
    77. }else{
    78. DoublyLinkedList* newlist = new DoublyLinkedList();
    79. list_ptr[count+1] = newlist;
    80. newlist->adjust(move);
    81. newlist->mp[key] = move;
    82. min_cnt.insert(count+1);
    83. }
    84. return value;
    85. }
    86. return -1;
    87. }
    88. void put(int key, int value) {
    89. if(cnt.count(key)){
    90. int count = cnt[key];//关键字查询前出现次数
    91. cnt[key]++;//put后访问次数+1
    92. DoublyLinkedList *list = list_ptr[count];
    93. list->mp[key]->value = value;//改值
    94. //从count中删除尾节点
    95. node* move = list->mp[key];
    96. list->remove(move);
    97. if(list->head->next == list->tail){//删空了
    98. list_ptr.erase(count);
    99. min_cnt.erase(count);
    100. }
    101. //加入count+1中的队首
    102. if(list_ptr.count(count+1)){
    103. DoublyLinkedList *list = list_ptr[count+1];
    104. list->adjust(move);
    105. list->mp[key] = move;
    106. }else{
    107. DoublyLinkedList* newlist = new DoublyLinkedList();
    108. list_ptr[count+1] = newlist;
    109. newlist->adjust(move);
    110. newlist->mp[key] = move;
    111. min_cnt.insert(count+1);
    112. }
    113. }else{//加入count = 1的队首
    114. if(now+1>capacity){//若超缓存则删除
    115. now--;
    116. int x = *min_cnt.begin();
    117. DoublyLinkedList *list = list_ptr[x];//出现次数最少的那条链表
    118. int toRemoveKey = list->remove_tail();
    119. cnt.erase(toRemoveKey);//key不在缓存中了,出现次数置0
    120. if(list->head->next == list->tail){
    121. min_cnt.erase(x);
    122. list_ptr.erase(x);
    123. }
    124. }
    125. node* newnode = new node(nullptr,nullptr,key,value);
    126. if(list_ptr.count(1)){
    127. DoublyLinkedList *list = list_ptr[1];
    128. list->adjust(newnode);
    129. list->mp[key] = newnode;
    130. }else{
    131. DoublyLinkedList* newlist = new DoublyLinkedList();
    132. newlist->adjust(newnode);
    133. newlist->mp[key] = newnode;
    134. list_ptr[1] = newlist;
    135. min_cnt.insert(1);
    136. }
    137. cnt[key] = 1;
    138. now++;
    139. }
    140. }
    141. };
  • 相关阅读:
    40道JAVA经典算法面试题(答案)
    逆向-还原代码之运算符++ (Interl 32)
    板凳----Linux/Unix 系统编程手册 25章 进程的终止
    代理IP与Socks5代理在网络安全与数据隐私中的关键作用
    2023NOIP A层联测6 万花筒
    【示波器专题】为什么示波器在最小幅度档位下显示的波形很粗?
    golang 实现四层负载均衡
    计算机体系结构:改进Cache性能笔记(非常详细)
    怎么办理工程监理资质,工程监理资质申请办理条件
    【025】mongoose V6.4开启debug日志打印
  • 原文地址:https://blog.csdn.net/liangcha_xyy/article/details/133438583