Niuke.net brushing questions-designing the LRU cache structure

Problem Description

Title description
Design the LRU cache structure. The size of the structure is determined during construction. Assuming the size is K, it has the following two functions
set (key, value): insert the record (key, value) into the structure
get(key): return key Corresponding value value
[Requirement]
The time complexity of the set and get methods is O(1)
Once a set or get operation of a certain key occurs, the record of this key is considered to be the most commonly used.
When the size of the cache exceeds K, remove the least frequently used record, that is, the oldest set or get.
If opt=1, the next two integers x, y represent set(x, y).
If opt=2, the next integer x represents get(x). If x has not appeared or has been removed, then Return -1
for each operation 2, output an answer

Example

Example 1

Enter
[[1,1,1],[1,2,2],[1,3,2],[2,1],[1,4,4],[2,2]],3

Output
[1,-1]

Explanation After
the first operation: The most frequently used record is ("1", 1) After the
second operation: The most frequently used record is ("2", 2), ("1", 1) becomes the most Infrequently used
after the third operation: the most frequently used record is ("3", 2), ("1", 1) or the least frequently used
after the fourth operation: the most frequently used record is ("1" , 1), ("2", 2) becomes the least frequently used
fifth operation: the size exceeds 3, so remove the least frequently used record ("2", 2) at this time, and add the record ( "4", 4), and is the most frequently used record, then ("3", 2) becomes the least frequently used record

Solutions

analysis

  1. With the help of Java's HashMap, there is no need to consider storage issues. We only need to implement lru through a linked list (the least recently used).
  2. Do not use Java's HashMap to realize the original hash storage (additional later)

Code

// 思路1
public class Solution {
    
      
        /**
     * lru design
     * @param operators int整型二维数组 the ops
     * @param k int整型 the k
     * @return int整型一维数组
     */
    public int[] LRU (int[][] operators, int k) throws Exception {
    
    

        LRU<Integer, Integer> lru = new LRU<>(k);
        List<Integer> list = new ArrayList<>();
        // write code here
        for (int i = 0; i < operators.length; i++) {
    
    
            int[] operator = operators[i];
            if (operator[0] == 1) {
    
    
                lru.put(operator[1], operator[2]);
            } else if (operator[0] == 2) {
    
    
                Integer temp = lru.get(operator[1]);
                if (temp == null) {
    
    
                    list.add(-1);
                } else {
    
    
                    list.add(temp);
                }
            }
        }

        int[] result = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
    
    
            result[i] = list.get(i);
        }
        return result;
    }
}

// 重新考虑,LRU:最近最少使用
// 数据结构:链表(可以通过现有的,也可以通过自定义的node节点)、map集合
// 方法:LRU:put方法,get方法;
// addFirst,添加到头部节点
// remove,移除节点
class LRU<K, V> {
    
    

    // 头尾节点作为空节点
    private Node head = new Node();
    private Node tail = new Node();
    // 记录K-Node映射,便于快速查找目标数据对应节点
    private HashMap<K, Node> map;
    private int maxSize;

    // 哈希链表节点类 Node
    private class Node {
    
    
        Node pre;
        Node next;
        K k;
        V v;

        public Node() {
    
    
        }

        // Node对外提供构造方法
        public Node(K k, V v) {
    
    
            this.k = k;
            this.v = v;
        }
    }

    // 初始化时必须传入最大可用内存容量
    public LRU(int maxSize) {
    
    
        this.maxSize = maxSize;
        // HashMap初始容量设置为 maxSize * 4/3,即达到最大可用内存时,HashMap也不会自动扩容浪费空间
        // 通过反向计算,可以避免map的扩容
        this.map = new HashMap<>(maxSize * 4 / 3);

        // 设置头节点的后置节点和尾节点的前置节点
        head.next = tail;
        tail.pre = head;
    }

    // 获取指定数据
    public V get(K key) {
    
    
        // 判断是否存在对应数据
        if (!map.containsKey(key)) {
    
    
            return null;
        }

        // 最新访问的数据移动到链表头,从链表中移除节点并追加到尾部
        Node node = map.get(key);
        remove(node);
        addFirst(node);
        return node.v;
    }

    // 更新旧数据或添加数据
    public void put(K key, V value) {
    
    
        System.out.println("K:" + key + ";V="+value);
        // 若存在旧数据则删除
        if (map.containsKey(key)) {
    
    
            Node node = map.get(key);
            remove(node);
        }

        // 新数据对应节点插入链表头
        Node node = new Node(key, value);
        map.put(key, node);
        addFirst(node);

        // 判断是否需要淘汰数据
        if (map.size() > maxSize) {
    
    
            node = removeLast();
            // 数据节点淘汰后,同时删除map中的映射
            map.remove(node.k);
        }
    }

    // 将指定节点插入链表头
    private void addFirst(Node node) {
    
    
        Node next = head.next;

        // 设置追加的节点为当前节点的后置节点
        head.next = node;
        // 设置当前节点的前置节点为头节点
        node.pre = head;

        node.next = next;
        next.pre = node;
    }

    // 从链表中删除指定节点
    private void remove(Node node) {
    
    
        Node pre = node.pre;
        Node next = node.next;

        // 跳过当前节点
        pre.next = next;
        // 设置下一个节点的前置节点
        next.pre = pre;

        node.next = null;
        node.pre = null;
    }

    // 淘汰数据
    private Node removeLast() {
    
    
        // 找到最近最久未使用的数据所对应节点
        Node node = tail.pre;

        // 淘汰该节点
        remove(node);

        return node;
    }
}

If you want to test, you can go directly to the link of Niuke.com to do the test

Design LRU cache structure

Guess you like

Origin blog.csdn.net/qq_35398517/article/details/113960142