面试算法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;
        }
    }
}
相关推荐
XianxinMao2 小时前
RLHF技术应用探析:从安全任务到高阶能力提升
人工智能·python·算法
hefaxiang2 小时前
【C++】函数重载
开发语言·c++·算法
exp_add33 小时前
Codeforces Round 1000 (Div. 2) A-C
c++·算法
查理零世3 小时前
【算法】经典博弈论问题——巴什博弈 python
开发语言·python·算法
神探阿航3 小时前
第十五届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组
java·算法·蓝桥杯
皮肤科大白4 小时前
如何在data.table中处理缺失值
学习·算法·机器学习
不能只会打代码5 小时前
蓝桥杯例题一
算法·蓝桥杯
OKkankan5 小时前
实现二叉树_堆
c语言·数据结构·c++·算法
Ciderw6 小时前
MySQL为什么使用B+树?B+树和B树的区别
c++·后端·b树·mysql·面试·golang·b+树
翻晒时光7 小时前
深入解析Java集合框架:春招面试要点
java·开发语言·面试