Leetcode 146:LRU缓存机制(哈希+双向链表,终于自己写出来了没bug的版本)

题解

  • 设计和实现一个 LRU (最近最少使用) 缓存机制,(如果不懂LRU机制的,需要先学习操作系统的LRU调度算法,先从理论上知道实现方式才行),支持以下操作: 获取数据 get 和 写入数据 put ,且两个操作都需要在O(1)复杂度下完成

  • LRU算法大意:给定一个固定大小的容器,容器中的事物代表需要使用的事物,以及一系列事物,每个事物都有自己的key, value值,其中key用来识别自身的身份,value则是内容。在容器没有满的时候,事物会不断向容器里面添加,容器满的时候,就把最早添加到容器中的事物删除,在添加事物的时候,如果遇到容器中已经有了的事物,就不往容器里面添加,而是直接把之前放入容器的事物调到容器的尾部,表示它为最近使用事物,从而实现淘汰掉的事物是最近最少使用的事物。

  • 题解:因为涉及到的添加与淘汰操作就是在头部和尾部执行的,我们可以直接考虑使用链表,每当加入一个数据,就往尾部添加,删除一个数据,就把头部删除,因为使用了链表,我们后面就将事物成为节点。

    • 考点1:但是get方法是查找LRU中的节点,我们知道,对于普通链表来说,查找的复杂度是o(n),但是题目要求使用o(1)的复杂度,因此我们不能直接使用普通的链表,而o(1)复杂度只有哈希能做到,因此我们使用一个额外空间来存储一个哈希映射Map<key, node>,这样,在get方法中,就可以通过哈希直接得到节点。
    • 考点2:既然叫做LRU算法, 那么调用get方法访问一个节点的时候,该节点就是最近访问的节点了,因此对应的位置移动到尾部,此外,put方法在放入具有相同key的数据的时候,也要移动节点的位置。
      • 对于链表来说,移动到尾部其实就是删除对应位置的节点,然后再在尾部添加节点,那么这个操作要如何实现o(1)的复杂度呢
      • 首先,链表必须是双向链表,因为删除节点的操作就是跨越当前节点建立前后节点的链接,从而孤立当前节点,`cur.next.prev = cur.prev; cur.prev.next=cur.next,如果不是双向链表,那么获取前一个节点的操作是非常耗时的。
      • 然后维护一个超级头部head和超级尾部tailtail.prev代表最近使用的节点,head.next代表可能即将被删除的节点,那么插入到尾部就是tail.prev.next = cur; cur.prev = tail.prev; cur.next = tail;
  • 实现:注意不要忘记长度的变化,以及节点位置变化之后对map的更新

class DLinkedNode {
    int key;
    int value;
    DLinkedNode prev;
    DLinkedNode next;
    public DLinkedNode() {}
    public DLinkedNode(int _key, int _value) {key = _key; value = _value;}
}

class LRUCache {
    private Map<Integer, DLinkedNode> m;
    private DLinkedNode head;
    private DLinkedNode tail;
    private int capacity;
    private int len;
    public LRUCache(int capacity) {
        this.capacity = capacity;
        len = 0;
        m = new HashMap<>();
        head = new DLinkedNode(); 
        tail = new DLinkedNode(); 
        head.next = tail;
        tail.prev = head;
    }
    
    public int get(int key) {
        DLinkedNode cur = m.get(key);
        if(cur == null) return -1;
        
        DLinkedNode cur_prev = cur.prev;
        DLinkedNode cur_next = cur.next; 
        cur_prev.next = cur_next; 
        cur_next.prev = cur_prev;
        addToTail(cur);
        m.replace(key, cur);
        return cur.value;
    }
    public void addToTail(DLinkedNode cur){
        //建立与倒数第二个节点的链接
        tail.prev.next = cur;
        cur.prev = tail.prev;
        //建立与尾巴的链接
        cur.next = tail;
        tail.prev = cur; 
    }
    public void put(int key, int value) {
        if(get(key) ==-1) {
            len++;     
            DLinkedNode tmp = new DLinkedNode(key, value);    
            addToTail(tmp);
            m.put(key, tmp);
            if(len > capacity){ 
                m.remove(head.next.key);
                head = head.next;
                head.prev = null;
                len--;
            }
        }
        else{
             m.get(key).value = value;
             m.replace(key, m.get(key));
        }
    }
}
  • 此题的启发:想要让查询时间编程o(1),无论什么数据结构(链表,树等等)都可以额外附加一个哈希映射,利用空间复杂度来换时间

题目

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值