查看: 1734| 回复: 0
跳转到指定楼层
上一主题 下一主题
收起左侧

[树/链表/图] Leetcode 146 LRU毫无新意题解

全局:

注册一亩三分地论坛,查看更多干货!

您需要 登录 才可以下载或查看附件。没有帐号?注册账号

x
本帖最后由 uncle Max 于 2021-11-4 14:42 编辑

最近和我一起刷题的小伙伴不和我一起刷题了,蛮伤感的。跟着她学到了很多东西,算得上刷题路上遇到的文昌贵人。不知道你会不会看得到这篇帖子,希望你能有个好的前程。感谢最近这段时间的陪伴和指引。

感伤完毕,对新手老大难问题 (LRU)。这篇文章我写在这里当做是自己的学习笔记,如果能够帮助到一些朋友,那也算是有点贡献。

参考内容
1. 王争 在某时间app上的 数据结构于算法之美 其中链表篇  归纳总结了很多底层的细节,能够帮助理解
2. 花花酱对于Leetcode 146的讲解  传送门 -> https://www.youtube.com/watch?v=q1Njd3NWvlY 能够快速帮你理解这道题的题目要求和注意的细节
3. 中国区力扣上的题解 传送门 -> https://leetcode-cn.com/problems ... xian-by-labuladong/ 系统的详细讲解了这道题的写法

正文内容

题目要求: 实现一个 最近最少使用 的设计题目
问题 :什么是最近最少使用?  
解答:想象一下你家里的书柜,上面放着的都是最近要看,看过,和在看的书。但是书柜大小有限,最好拿的位置肯定是最近最常看的书。因此,当某些书你确定最近不会看的时候,你会把它收纳到盒子里放在一个不碍事的角落里。那么这个过程就是实现了一个最近最少使用的设计。

题目逻辑:1. 当 get 的时候
                           a. 若已经存在,将之前存在的删除。在从头部插入
                           b. 若不存在 返回-1
                 2.  当 put的时候
                           a. 若不存在
                                 I. 查看容器是否已满,满了的话把最后一位删除。不满就直接插入到头节点
                           b. 若存在
                                 i. 将之前的删除,从头部插入
题目一些引申的requirement:
         1. 需要支持random访问 时间需要是 o(1)
         2. remove last entry 时间需要o(1)
         3. add/ move an entry 时间需要 o(1)

题目数据结构策略:使用HashMap + double linked list来实现
问:为什么要用HashMap?
答:单纯使用list 无法支持在O(1)的时间做到随机访问 Array可以做到O(1)的时间随机访问,但是Array在remove 和 add 的时候可能会造成 o(n)的时间

问:为什么要使用double linked list? 单链表不就可以完成2 和 3 了吗
答:使用双链表的原因是在于,可以很快的找到它的前节点。虽然这样存储会消耗额外的空间(extra space for pre-pointer )但是以空间换时间的方式在这道题是可取的。因为我们可能要频繁涉及到随机删除某一个entry。单链表的话,我们还需要先循环一遍记录pre 节点,在来找当前节点。(插入也会有同样的问题,但是这道题不太涉及插入,但可以延伸了解双向链表这一特性)

解题思路

手撸一个 双向链表的class (你也可以去调用LinkedHashMap 或者 double linked list anyway 别问!问就是我要装逼)
class Node{
        Node prev;
        Node next;
        int key;
        int val;
        public Node(int k,int v){
            key = k;
            val = v;
        }
    }


初始化 capacity 和double linked list的对应关系
public LRUCache(int capacity) {
        cap = capacity;
        map = new HashMap<>();
        size = 0;
        head = new Node(-1,-1);
        tail = new Node(-1,-1);
        head.next = tail;
        tail.prev = head;
    }

构建remove function 方便调用(可读性 和 可扩展性 还有复用性)
public void remove(Node node){
//这里给新手同学讲一个linked list的一个技巧,处理对应关系时,为了保证正确性和逻辑性,不造成溢出。(举个不太好的例子) 先找好小三和小三确立好关系,在把现任甩了 (这个例子应该会被骂吧 方便记忆 骂就骂吧)
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }
建议新手同学 画画图掌握一下对应关系

构建一个addHead function 因为我们要频繁向头结点插入
public void addHead(Node node){
        node.prev = head;
        node.next = head.next;
        head.next = node;
        node.next.prev = node;
    }

接下来就是 get function
public int get(int key) {
        Node node = map.get(key);
        if(node==null) return -1;
        remove(node);
        addHead(node);
        return node.val;
    }

最终大boss put
public void put(int key, int value) {
        Node node = map.get(key);
      //这里就是我之前说的 先查看有没有
        if(node==null){
            node = new Node(key,value);
            map.put(key,node);
            addHead(node);
         //在查看size的情况
            if(size==cap){
                map.remove(tail.prev.key);
                remove(tail.prev);
            }
            else{
                size++;
            }
        }
//如果有的话 就按get哪样去处理
        else{
            node.val = value;
            map.put(key,node);
            remove(node);
            addHead(node);
        }
    }


代码详情如下
class LRUCache {
    class Node{
        Node prev;
        Node next;
        int key;
        int val;
        public Node(int k,int v){
            key = k;
            val = v;
        }
    }
    Map<Integer,Node> map;
    Node head;
    Node tail;
    int cap;
    int size;
    public LRUCache(int capacity) {
        cap = capacity;
        map = new HashMap<>();
        size = 0;
        head = new Node(-1,-1);
        tail = new Node(-1,-1);
        head.next = tail;
        tail.prev = head;
    }


    public void remove(Node node){
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    public void addHead(Node node){
        node.prev = head;
        node.next = head.next;
        head.next = node;
        node.next.prev = node;
    }

    public int get(int key) {
        Node node = map.get(key);
        if(node==null) return -1;
        remove(node);
        addHead(node);
        return node.val;
    }

    public void put(int key, int value) {
        Node node = map.get(key);
        if(node==null){
            node = new Node(key,value);
            map.put(key,node);
            addHead(node);
            if(size==cap){
                map.remove(tail.prev.key);
                remove(tail.prev);

            }
            else{
                size++;
            }
        }
        else{
            node.val = value;
            map.put(key,node);
            remove(node);
            addHead(node);
        }
    }
}

一直很头疼这LRU问题,当接触了大量的相关知识之后,花了两个小时仔细研读了一番,其实看通了之后就怎么回事。逻辑很重要。
文后,在碎碎念一下。真的蛮感谢哪位一起刷题的同学,她在思想上教育了我,如何去刷题。刷题是为了巩固你的逻辑,将逻辑应用到类似的题目之中。考算法,不是考谁背的多,背的快,而是你的思维逻辑和分析方法。这就是为什么有的同学可以在没有最优解,没有 做完题目的情况下依旧能拿到offer的原因,你让人看到了你的思考问题的方法和过程,路径正确,就是走得快慢的事了。


评分

参与人数 2大米 +11 收起 理由
Ben_Zz + 1 给你点个赞!
14417335 + 10

查看全部评分


上一篇:请问大家数组题array是不是不用刷?
下一篇:力扣的题目难度系数
您需要登录后才可以回帖 登录 | 注册账号
隐私提醒:
  • ☑ 禁止发布广告,拉群,贴个人联系方式:找人请去🔗同学同事飞友,拉群请去🔗拉群结伴,广告请去🔗跳蚤市场,和 🔗租房广告|找室友
  • ☑ 论坛内容在发帖 30 分钟内可以编辑,过后则不能删帖。为防止被骚扰甚至人肉,不要公开留微信等联系方式,如有需求请以论坛私信方式发送。
  • ☑ 干货版块可免费使用 🔗超级匿名:面经(美国面经、中国面经、数科面经、PM面经),抖包袱(美国、中国)和录取汇报、定位选校版
  • ☑ 查阅全站 🔗各种匿名方法

本版积分规则

>
快速回复 返回顶部 返回列表