返回

NC93 设计 LRU 缓存结构:理解 Map 应用,巧妙解题

前端

导言

最近,笔者在牛客网上遇到了一道 NC93 LRU(最近最少使用)算法题。虽然这道题并不算难,但它却巧妙地考查了我们对 Map 数据结构的理解。在解决这道题目的过程中,笔者也深切地感受到自己对 Map 的掌握还有待提升,因此花费了一番时间深入钻研。

什么是 LRU 缓存?

LRU 缓存(Least Recently Used Cache)是一种广泛应用于计算机系统中的缓存机制。它的主要思想是:当缓存空间不足时,将最长时间未被使用的元素(即最近最少使用的元素)从缓存中移除,为新元素腾出空间。

NC93 算法题解

NC93 算法题要求我们实现一个 LRU 缓存结构,支持以下操作:

  • set(key, value):将一个键值对插入缓存中。
  • get(key):获取缓存中指定键对应的值。

题解思路

为了实现 LRU 缓存,我们可以使用哈希表(Map)来存储键值对,同时使用一个双向链表来维护缓存中的元素顺序。具体来说,哈希表中的键对应着缓存中的元素,而值对应着该元素在链表中的位置。当进行 set 操作时,如果键已存在于哈希表中,则更新其值并将其移动到链表头部;如果键不存在,则将其插入哈希表中并添加到链表头部。当进行 get 操作时,直接从哈希表中获取元素并将其移动到链表头部。

代码示例

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class LRUCache {

    private int capacity;
    private Map<Integer, Integer> cache;
    private DoublyLinkedList list;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        cache = new LinkedHashMap<>();
        list = new DoublyLinkedList();
    }

    public int get(int key) {
        if (!cache.containsKey(key)) {
            return -1;
        }
        list.moveToHead(cache.get(key));
        return cache.get(key);
    }

    public void set(int key, int value) {
        if (cache.containsKey(key)) {
            list.moveToHead(cache.get(key));
            cache.put(key, value);
        } else {
            if (cache.size() == capacity) {
                int tailKey = list.removeTail();
                cache.remove(tailKey);
            }
            list.addToHead(key);
            cache.put(key, value);
        }
    }

    private class DoublyLinkedList {

        private Node head;
        private Node tail;

        public void addToHead(int key) {
            Node newHead = new Node(key);
            if (head == null) {
                head = newHead;
                tail = newHead;
            } else {
                newHead.next = head;
                head.prev = newHead;
                head = newHead;
            }
        }

        public void moveToHead(int key) {
            Node node = findNode(key);
            if (node == null) {
                return;
            }
            if (node == head) {
                return;
            }
            if (node == tail) {
                tail = tail.prev;
                tail.next = null;
            } else {
                node.prev.next = node.next;
                node.next.prev = node.prev;
            }
            addToHead(key);
        }

        public int removeTail() {
            if (tail == null) {
                return -1;
            }
            int key = tail.key;
            tail = tail.prev;
            if (tail != null) {
                tail.next = null;
            } else {
                head = null;
            }
            return key;
        }

        private Node findNode(int key) {
            Node curr = head;
            while (curr != null) {
                if (curr.key == key) {
                    return curr;
                }
                curr = curr.next;
            }
            return null;
        }

        private class Node {

            private int key;
            private Node prev;
            private Node next;

            public Node(int key) {
                this.key = key;
            }
        }
    }
}

总结

通过对 NC93 LRU 缓存设计算法题的深入分析,我们不仅理解了 LRU 缓存的原理,更重要的是领会了 Map 数据结构在算法实现中的巧妙应用。希望这篇文章能为读者带来启发,帮助大家更好地理解算法题并提升自己的编码能力。