面试算法31:最近最少使用缓存

题目

请设计实现一个最近最少使用(Least Recently Used,LRU)缓存,要求如下两个操作的时间复杂度都是O(1)。

  • get(key):如果缓存中存在键key,则返回它对应的值;否则返回-1。
  • put(key,value):如果缓存中之前包含键key,则它的值设为value;否则添加键key及对应的值value。在添加一个键时,如果缓存容量已经满了,则在添加新键之前删除最近最少使用的键(缓存中最长时间没有被使用过的元素)。

分析

哈希表HashMap的get操作和put操作的时间复杂度都是O(1),但普通的哈希表无法找出最近最少使用的键,因此,需要在哈希表的基础上进行改进。

由于需要知道缓存中最近最少使用的元素,因此可以把存入的元素按照访问的先后顺序存入链表中。每次访问一个元素(无论是通过get操作还是通过put操作),该元素都被移到链表的尾部。这样,位于链表头部的元素就是最近最少使用的。

下面考虑如何实现把一个节点移到链表的尾部。这实际上包含两个步骤,首先要把节点从原来的位置删除,然后把它添加到链表的尾部。需要注意的是,在链表中删除一个节点,实际上是把它的前一个节点的next指针指向它的下一个节点。如果这个链表是单向链表,那么找到一个节点的前一个节点需要从链表的头节点开始顺序扫描每个节点,也就需要O(n)的时间。

为了快速找到一个节点的前一个节点从而实现用O(1)的时间删除一个节点,可以用双向链表来存储缓存中的元素。在双向链表中查找一个节点的前一个节点只需要顺着prev指针向前走一步,时间复杂度为O(1)。

因此,设计最近最少使用缓存需要结合哈希表和双向链表的特点。哈希表的键就是缓存的键,哈希表的值为双向链表中的节点,每个节点都是一组键与值的数对。

java 复制代码
public class Test {
    public static void main(String[] args) {
        LRUCache lruCache = new LRUCache(4);
        lruCache.put(1,1);
        lruCache.put(2,2);
        lruCache.put(3,3);
        lruCache.put(4,4);

        ListNode node1 = lruCache.head;
        while (node1 != null){
            System.out.println(node1.value);
            node1 = node1.next;
        }
        System.out.println("-------------------------");

        lruCache.get(2);
        ListNode node2 = lruCache.head;
        while (node2 != null){
            System.out.println(node2.value);
            node2 = node2.next;
        }
        System.out.println("-------------------------");

        lruCache.put(1,8);
        ListNode node3 = lruCache.head;
        while (node3 != null){
            System.out.println(node3.value);
            node3 = node3.next;
        }
        System.out.println("-------------------------");

        lruCache.put(5,5);
        ListNode node4 = lruCache.head;
        while (node4 != null){
            System.out.println(node4.value);
            node4 = node4.next;
        }
    }

    static class ListNode{
        public int key;
        public int value;
        public ListNode next;
        public ListNode prev;

        public ListNode(int k,int v){
            key = k;
            value = v;
        }
    }

    static class LRUCache{
        private ListNode head;
        private ListNode tail;
        private Map<Integer,ListNode> map;
        int capacity;

        public LRUCache(int cap){
            map = new HashMap<>();

            head = new ListNode(-1,-1);
            tail = new ListNode(-1,-1);
            head.next = tail;
            tail.prev = head;

            capacity = cap;
        }

        public int get(int key){
            ListNode node = map.get(key);
            if (node == null){
                return -1;
            }

            moveToTail(node,node.value);

            return node.value;
        }

        public void put(int key,int value){
            if (map.containsKey(key)){
                moveToTail(map.get(key),value);
            }else {
                if (map.size() == capacity){
                    ListNode toBeDeleted = head.next;
                    deleteNode(toBeDeleted);

                    map.remove(toBeDeleted.key);
                }

                ListNode node = new ListNode(key,value);
                intsertToTail(node);

                map.put(key,node);
            }
        }

        private void moveToTail(ListNode node,int newValue){
            deleteNode(node);

            node.value = newValue;
            intsertToTail(node);
        }

        private void deleteNode(ListNode node){
            node.prev.next = node.next;
            node.next.prev = node.prev;
        }

        private void intsertToTail(ListNode node){
            tail.prev.next = node;
            node.prev = tail.prev;
            node.next = tail;
            tail.prev = node;
        }
    }
}
相关推荐
倔强青铜三11 小时前
苦练Python第46天:文件写入与上下文管理器
人工智能·python·面试
天天扭码15 小时前
来全面地review一下Flex布局(面试可用)
前端·css·面试
Mor_15 小时前
UE5 网络通信协议学习笔记
面试
沐怡旸15 小时前
【底层机制】std::unique_ptr 解决的痛点?是什么?如何实现?怎么正确使用?
c++·面试
前端缘梦15 小时前
Vue Keep-Alive 组件详解:优化性能与保留组件状态的终极指南
前端·vue.js·面试
聚客AI16 小时前
🙋‍♀️Transformer训练与推理全流程:从输入处理到输出生成
人工智能·算法·llm
前端付豪18 小时前
1、震惊!99% 前端都没搞懂的 JavaScript 类型细节
前端·javascript·面试
Java水解18 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
大怪v18 小时前
前端:人工智能?我也会啊!来个花活,😎😎😎“自动驾驶”整起!
前端·javascript·算法
洛小豆20 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试