面试算法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;
        }
    }
}
相关推荐
AC使者16 分钟前
#B1630. 数字走向4
算法
冠位观测者20 分钟前
【Leetcode 每日一题】2545. 根据第 K 场考试的分数排序
数据结构·算法·leetcode
古希腊掌管学习的神1 小时前
[搜广推]王树森推荐系统笔记——曝光过滤 & Bloom Filter
算法·推荐算法
麦香--老农1 小时前
windows 钉钉缓存路径不能修改 默认C盘解决方案
缓存·钉钉
qystca1 小时前
洛谷 P1706 全排列问题 C语言
算法
浊酒南街1 小时前
决策树(理论知识1)
算法·决策树·机器学习
就爱学编程1 小时前
重生之我在异世界学编程之C语言小项目:通讯录
c语言·开发语言·数据结构·算法
C++忠实粉丝1 小时前
Redis 介绍和安装
数据库·redis·缓存
学术头条1 小时前
清华、智谱团队:探索 RLHF 的 scaling laws
人工智能·深度学习·算法·机器学习·语言模型·计算语言学
丰云2 小时前
一个简单封装的的nodejs缓存对象
缓存·node.js