常见数据结构

LRU Cache

非常经典的一道题,很考察设计能力,以及data structure的选用问题

一句话描述这题的话,就是一个 “头 + 尾 dummy node 的双向链表” + “HashMap”.

  • 先沟通各种 input/output
  • 思考流程
  • 把流程用 comments (另一种颜色 mark 笔写下来)
  • 把发现流程中重复率高的地方写成 function
  • 模块化填充,改错。
public class LRUCache {
    class Node {
        int key;
        int value;
        Node prev;
        Node next;

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

    private HashMap<Integer, Node> map = new HashMap<>();
    // least used node
    private Node dummyHead = new Node(-1, -1);
    // most used node
    private Node dummyTail = new Node(-1, -1);
    private int capacity;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        dummyHead.next = dummyTail;
        dummyTail.prev = dummyHead;
    }

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

        Node cur = map.get(key);

        // move the node to tail
        if (cur.next != dummyTail) {
            // disjoint from linkedlist
            cur.next.prev = cur.prev;
            cur.prev.next = cur.next;
            moveToTail(cur);
        }

        return cur.value;
    }

    public void moveToTail(Node node) {
        // connect four pointer
        node.next = dummyTail;
        node.prev = dummyTail.prev;
        node.prev.next = node;
        dummyTail.prev = node;
    }

    public void set(int key, int value) {
        // have to update hashmap
        if (get(key) != -1) {
            map.get(key).value = value;
            return;
        }

        // before add new node, need to check the capacity
        if (map.size() == capacity) {
            // remove first
            dummyHead.next = dummyHead.next.next;
            dummyHead.next.prev = dummyHead;
            // update map
            map.remove(key);
        }

        Node newNode = new Node(key, value);
        moveToTail(newNode);
        // update hashmap
        map.put(key, newNode);
    }

}

Sliding Window Maximum

这道题的暴力解法是O(nk),用两个指针维护一个window,然后每次迭代去寻找window里面最大的数。

如何优化到O(n)?

因为我们只关心max,所以维护一个数据结构,

  • 可以维护插入的顺序,方便的移除已经不在window之内的数。
  • 同时每次add一个数之前把小于它的数都pop掉,让在这个数据结构里面的数呈现单调性。
  • 由于每一个元素只会被 add 和 remove 一次,整个流程的均摊复杂度就是 O(1).

因此我们可以用deque来实现,从尾端加入新数,把小于新数的数都pop掉,从头端去看max。因为我们维持了插入的顺序,所以头端的数是window的第一个数。

Deque 里面要存 index,而不是值。这样可以方便移除不在window里面的数。

public int[] maxSlidingWindow(int[] nums, int k) {
    Deque<Integer> deque = new LinkedList<>();
    int n = nums.length;
    int[] result = new int[n - k + 1];

    for(int i = 0; i < n; i++) {
        // remove shift-out element
        if(!deque.isEmpty() && deque.peekFirst() <= i - k) {
            deque.pollFirst();
        }

        // remove ints smaller than shift-in int
        while(!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
            deque.pollLast();
        }

        // add new one
        deque.offerLast(i);

        // when the window has formed
        if(i >= k - 1) {
            result[i + 1- k] = nums[deque.peekFirst()];
        }
    }

    return result;
}

results matching ""

    No results matching ""