LeetCode 146.155 设计应用题

436 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

今天做两道设计题,分别对应LeetCode 146.LRU缓存LeetCode 155.最小栈。具体题目可点击链接。

解题思路

146. LRU缓存

对于146这题,要求设计一个满足LRU缓存约束的数据结构,并且存入元素和获取元素都是O(1)O(1)的时间复杂度。获取元素为O(1)O(1),很容易想到数组,而插入元素O(1)O(1)这则是链表,有什么数据结构结合了这两点吗,那就是Map。因此本题主要数据结构就是使用HashMap存储元素(关于HashMap的底层原理,看这里),但如何记录每个key的使用次数呢?这才是关键。

这里采用的是双向链表存储这种使用关系,双向链表的头部指向的是最近使用的节点,每次元素使用都采用头插法,这样可以保证头部指向的节点必定是最近使用的,而尾部则是最近最少使用的。当元素数量大于容量的时候,我们只需要通过尾部节点获取节点的key,之后删除即可。因此我们需要设计的双向链表结构为:

class DoubleList{
    private int key;
    private int value;
    DoubleList pre;
    DoubleList next;
    public DoubleList(){}
    public DoubleList(int key, int value){
        this.key = key;
        this.value = value;
    }

注意此处的key必须要,因为需要根据尾部节点来找map中的key。代码如下:

class DoubleList{

    private int key;
    private int value;
    DoubleList pre;
    DoubleList next;
    public DoubleList(){}
    public DoubleList(int key, int value){
        this.key = key;
        this.value = value;
    }

}

private HashMap<Integer, DoubleList> map;
private int capacity;
DoubleList head;
DoubleList tail;

public LRUCache(int capacity) {
    this.capacity = capacity;
    map = new HashMap<>();
    head = new DoubleList();
    tail = new DoubleList();
    head.next = tail;
    tail.pre = head;
}

public int get(int key) {
    if(!map.containsKey(key)){
        return -1;
    }else{
        removeNode(map.get(key));
        addNode(map.get(key));
        return map.get(key).value;
    }
}

public void put(int key, int value) {
    DoubleList node = map.get(key);
    if(node == null){
        if(map.size()>=capacity){
            DoubleList tailNode = tail.pre;
            removeNode(tailNode);
            map.remove(tailNode.key);
            DoubleList newNode = new DoubleList(key, value);
            addNode(newNode);
            map.put(key, newNode);

        }else {
            DoubleList newNode = new DoubleList(key, value);
            addNode(newNode);
            map.put(key, newNode);
        }
    }else {
        removeNode(node);
        DoubleList newNode = new DoubleList(key, value);
        addNode(newNode);
        map.put(key, newNode);
    }
}

// 靠近头的是最近使用的 靠近尾则是最近最少未使用
public void addNode(DoubleList node){
    node.next = head.next;
    head.next.pre = node;
    head.next = node;
    node.pre = head;
}

public void removeNode(DoubleList node){
    node.pre.next = node.next;
    node.next.pre = node.pre;
}

155.最小栈

对于155题,pushpop以及 topstack中都包含原型函数可以调用,因此本题的关键是如何获取最小值。

简单的思路是我们可以每次push都保存当前值和最小值,这样每次通过栈顶就可以得到最小值,而其它的操作都是栈的基本使用,代码如下:

private Stack<int[]> stack;

public MinStack() {
    stack = new Stack<>();
}

public void push(int val) {
    if(!stack.isEmpty()){
        int min = Math.min(val, stack.peek()[1]);
        stack.push(new int[]{val, min});
    }else {
        stack.push(new int[]{val, val});
    }
}

public void pop() {
    stack.pop();
}

public int top() {
    return stack.peek()[0];
}

public int getMin() {
    return stack.peek()[1];
}

还有一种思路是使用一个额外的栈来保存最小元素,每次入栈出栈都同时对两个栈进行操作,而最小栈的栈顶也对应当前栈的最小值,代码如下:

private Stack<Integer> stack;
private Stack<Integer> minStack;

public MinStack() {
    stack = new Stack<>();
    minStack = new Stack<>();
}

public void push(int val) {
    if(!stack.isEmpty()){
        int min = Math.min(val, minStack.peek());
        stack.push(val);
        minStack.push(min);
    }else {
        stack.push(val);
        minStack.push(val);
    }
}

public void pop() {
    stack.pop();
    minStack.pop();
}

public int top() {
    return stack.peek();
}

public int getMin() {
    return minStack.peek();
}