• LeetCode146.LRU缓存


    写了一个小时,终于把示例跑过了,没想到啊提交之后第19/22个测试用例没过

    我把测试用例的输出复制在word上看看和我的有什么不同,没想到有18页的word,然后我一直检查终于找出了问题,而且这个bug真的太活该了,感觉只有傻屌才能写出这种bug,以下是我之前的代码:

    1. class LRUCache {
    2. HashMap map;
    3. int cap;
    4. int useIndex;
    5. HashMap lastUseMap;
    6. public LRUCache(int capacity) {
    7. map = new HashMap();
    8. lastUseMap = new HashMap();
    9. cap = capacity;
    10. useIndex =0;
    11. }
    12. public int get(int key) {
    13. if(map.containsKey(key)){
    14. lastUseMap.put(key,useIndex);
    15. useIndex++;
    16. return map.get(key);
    17. }else{
    18. return -1;
    19. }
    20. }
    21. public void put(int key, int value) {
    22. if(!map.containsKey(key)){
    23. if(map.size() == cap){
    24. int min = 10000;
    25. int delKey = -1;
    26. Set> entrySet = lastUseMap.entrySet();
    27. for (Map.Entry entry : entrySet) {
    28. if(entry.getValue() < min){
    29. min = entry.getValue();
    30. delKey = entry.getKey();
    31. }
    32. }
    33. map.remove(delKey);
    34. lastUseMap.remove(delKey);
    35. }
    36. }
    37. map.put(key,value);
    38. lastUseMap.put(key,useIndex);
    39. useIndex++;
    40. }
    41. }

    我想讲我的算法思想,最后再讲里面的那个bug。

    我的想法是,用hashmap来进行put和get操作,这样就不用自己写真正put和get的代码了(其实一开始看到题目写put和get算法复杂度是O1我用的是数组,写了很久没写出来,直接换成了hashmap,因为一开始我以为hashmap的算法复杂度大于1,但其实不是的,因为hashmap是数组--->数组+链表 ---->数组+红黑树,最简单的情况是用hash函数直接算出数组下标,算法复杂度是O1,如果数组的这个位置上是个链表,那么还要用O(n)遍历链表,如数组的这个位置上是个红黑树,那么还要用O(nlogn)遍历红黑树,但大多数情况还是O(1))

    那么put和get操作解决了,只需要解决过期淘汰就可以了,我是用一个

     HashMap map;

    进行正真的putget缓存操作,然后用一个

     int useIndex;

    来记录每次get和put操作的序号(从0开始,每次自增,如果get失败就不自增),然后再用一个map

    HashMap lastUseMap;
    

    来记录map中的每个元素的最新一次的操作序号。

    构造函数就不用说了,把这几个属性初始化以下就行。

    先讲get方法,如果map中没有这个key返回-1,如果有这个key,把lastUseMap中的这个key的value更新为本次操作的序号,再把序号加一,再把map中这个key的value返回。

    再讲讲put方法,先对size进行判断,如果达到最大容量cap那么就要删除掉map中的一个最久未使用的元素,要找出最久未使用的元素只需要对lastUseMapz进行一次遍历即可,这个就属于基操了,先定义个较大数min,然后把遍历出来的value和min进行比较,如果小于min就把min更新为这个value,找到了这个最久没用的元素后再map和lastUseMap中把这个元素删除就可以了,然后是进行put操作,除了把key和value put进map以外还要记得把key和当前操作序号put进lastUseMap,然后操作序号自增。

    需要注意的一点是,在尽心put操作之前先看看map里面有没有这个key,如果有直接put就行不用删除,这是个坑,有个测试用例就是这个。

    最后讲讲那个傻逼bug,就是min的初值,我当时也没想到操作次数会达到18页word啊,就直接给了10000,所以在第19个测试用例报错了,只要把min的初值赋为Integer.MAX_VALUE就可以了。

    再看看官方题解吧:

    题解用的是hsahMap+双向链表的做法,并且它并没有用封装好的LinkedList,而是自己写一个简介的链表,以下是题解代码:

    1. public class LRUCache {
    2. class DLinkedNode {
    3. int key;
    4. int value;
    5. DLinkedNode prev;
    6. DLinkedNode next;
    7. public DLinkedNode() {}
    8. public DLinkedNode(int _key, int _value) {key = _key; value = _value;}
    9. }
    10. private Map cache = new HashMap();
    11. private int size;
    12. private int capacity;
    13. private DLinkedNode head, tail;
    14. public LRUCache(int capacity) {
    15. this.size = 0;
    16. this.capacity = capacity;
    17. // 使用伪头部和伪尾部节点
    18. head = new DLinkedNode();
    19. tail = new DLinkedNode();
    20. head.next = tail;
    21. tail.prev = head;
    22. }
    23. public int get(int key) {
    24. DLinkedNode node = cache.get(key);
    25. if (node == null) {
    26. return -1;
    27. }
    28. // 如果 key 存在,先通过哈希表定位,再移到头部
    29. moveToHead(node);
    30. return node.value;
    31. }
    32. public void put(int key, int value) {
    33. DLinkedNode node = cache.get(key);
    34. if (node == null) {
    35. // 如果 key 不存在,创建一个新的节点
    36. DLinkedNode newNode = new DLinkedNode(key, value);
    37. // 添加进哈希表
    38. cache.put(key, newNode);
    39. // 添加至双向链表的头部
    40. addToHead(newNode);
    41. ++size;
    42. if (size > capacity) {
    43. // 如果超出容量,删除双向链表的尾部节点
    44. DLinkedNode tail = removeTail();
    45. // 删除哈希表中对应的项
    46. cache.remove(tail.key);
    47. --size;
    48. }
    49. }
    50. else {
    51. // 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
    52. node.value = value;
    53. moveToHead(node);
    54. }
    55. }
    56. private void addToHead(DLinkedNode node) {
    57. node.prev = head;
    58. node.next = head.next;
    59. head.next.prev = node;
    60. head.next = node;
    61. }
    62. private void removeNode(DLinkedNode node) {
    63. node.prev.next = node.next;
    64. node.next.prev = node.prev;
    65. }
    66. private void moveToHead(DLinkedNode node) {
    67. removeNode(node);
    68. addToHead(node);
    69. }
    70. private DLinkedNode removeTail() {
    71. DLinkedNode res = tail.prev;
    72. removeNode(res);
    73. return res;
    74. }
    75. }

    hashMap的key是key,value是链表的节点。链表中越头部的节点是越新的节点,越再尾部的节点是越久未使用的节点。

    get方法只需要通过key拿到node节点,如果为null直接返回-1,否则返回node.val,并且需要把这个node移到链表的最前面。

    put方法也是通过key拿到node节点,如果node不存在就创建一个node,然后把key和这个node放进hashMap,把这个node放到链表的头部,然后size++,如果这个时候size>最大容量了那么就把尾部节点删除,并且再map中删除这个key,size--;如果node存在直接把node的value改成参数value然后把node移到链表头部,剩下的方法都是关于双向链表的操作了。

  • 相关阅读:
    伊朗黑客对以色列科技行业发起恶意软件攻击
    1521_AURIX TC275 FPI总线系统寄存器
    Isito 入门(八):金丝雀发布
    tls会话交互过程之一
    使用V2V功能将VMware平台虚拟机迁移至OpenStack平台
    进大厂必备的 Java 八股文大全(2022 最强精简易懂版)
    基于虚拟化技术的5G核心网资源配置算法
    Mycat配置文件详解
    线程安全和synchronized关键字
    PyTorch-12 GAN、WGAN
  • 原文地址:https://blog.csdn.net/qq_61009660/article/details/134352612