Algorithm Notes
  • Introduction
  • Search & Backtracking 搜索与回溯
    • Tree 与 BackTracking 的比较
    • Subsets, Combination 与 Permutation
    • Subsets & Combinations & Combination Sum
    • 枚举法
    • N 皇后 + 矩阵 Index Trick
    • Sudoku 数独 + 矩阵 Index Trick
    • Word Ladder I & II
    • Number of ways 类
    • DFS flood filling
    • Strobogrammatic 数生成
    • String 构造式 DFS + Backtracking
    • Word Pattern I & II
    • (G) Binary Watch
    • (FB) Phone Letter Combination
    • 常见搜索问题的迭代解法
  • String,字符串类
    • 多步翻转法
    • Substring 结构和遍历
    • Palindrome 问题
    • Palindrome Continued
    • String / LinkedList 大数运算
    • 序列化与压缩
    • 5/24 String 杂题
    • Knuth–Morris–Pratt 字符串匹配
    • Lempel–Ziv–Welch 字符串压缩算法
    • (G) Decode String
    • (G) UTF-8 Validation
  • Binary Tree,二叉树
    • 各种 Binary Tree 定义
    • LCA 类问题
    • 三序遍历,vertical order
    • Post order traversal 的应用
    • Min/Max/Balanced Depth
    • BST
    • 子树结构
    • Level Order traversal
    • Morris 遍历
    • 修改结构
    • 创建 / 序列化
    • 子树组合,BST query
    • 路径与路径和
    • NestedInteger 类
    • (FB) 从 Binary Tree Path 看如何递归转迭代
    • (FB) Binary Tree Path 比较路径大小
    • 比较好玩的 Binary Tree 概率题
  • Segment & Fenwick Tree,区间树
    • Segment Tree 基础操作
    • Segment Tree 的应用
    • Fenwick Tree (Binary Indexed Tree)
    • Range Sum Query 2D - Immutable
  • Union-Find,并查集
    • Union-Find,并查集基础
    • Union-Find, 并查集应用
  • Dynamic Programming, 动态规划
    • 6/20, 入门 House Robber
    • 7/12, Paint Fence / House
    • 6/24, 滚动数组
    • 6/24, 记忆化搜索
    • 6/24, 博弈类 DP
    • 博弈类DP, Flip Game
    • 6/25, 区间类DP
    • 6/27, subarray 划分类,股票
    • 7/2, 字符串类
    • Bomb Enemies
    • 8/2,背包问题
    • (G) Max Vacation
    • (11/4新增) AST 子树结构 DP
  • LinkedList,链表
    • 6/9, LinkedList,反转与删除
    • 6/11, LinkedList 杂题
    • (FB) 链表的递归与倒序打印
  • LinkedIn 面经,算法题
    • 6/17, LinkedIn 面经题
    • 6/28, LinkedIn 面经题
    • 7/6, LinkedIn 面经
    • Shortest Word Distance 类
    • DFA Parse Integer
  • Two Pointers,双指针
    • 3 Sum, 3 Sum Closest / Smaller, 4 Sum
    • 对撞型,灌水类
    • 对撞型,partition类
    • Wiggle Sort I & II
    • 双指针,窗口类
    • 双指针,窗口类
    • Heap,排序 matrix 中的 two pointers
  • Bit & Math,位运算与数学
    • Bit Manipulation,对于 '1' 位的操作
    • Math & Bit Manipulation, Power of X
    • 坐标系 & 数值计算类
    • Add Digits
    • 用 int 做字符串 signature
  • Interval 与 扫描线
    • Range Addition & LCS
    • 7/5, Interval 类,扫描线
  • Trie,字典树
    • 6/9, Trie, 字典树
  • 单调栈,LIS
    • 4/13 LIS
    • 栈, 单调栈
    • Largest Divisible Subset
  • Binary Search 类
    • Matrix Binary Search
    • Array Binary Search
    • Find Peak Element I & II
    • **Median of Two Sorted Arrays
  • Graph & Topological Sort,图 & 拓扑排序
    • 有向 / 无向 图的基本性质和操作
    • 拓扑排序, DFS 做法
    • 拓扑排序, BFS 做法
    • Course Schedule I & II
    • Alien Dictionary
    • Undirected Graph, BFS
    • Undirected Graph, DFS
    • 矩阵,BFS 最短距离探索
    • 欧拉回路,Hierholzer算法
    • AI, 迷宫生成
    • AI, 迷宫寻路算法
    • (G) Deep Copy 无向图成有向图
  • 括号与数学表达式的计算
  • Iterator 类
  • Majority Element,Moore's Voting
  • Matrix Inplace Operations
  • 常见数据结构设计
  • (G) Design / OOD 类算法题
  • 随机算法 & 数据结构
  • (FB) I/O Buffer
  • (FB) Simplify Path, H-Index I & II
  • (FB) Excel Sheet, Remove Duplicates
  • Integer 的构造,操作,序列化
  • Frequency 类问题
  • Missing Number 类,元素交换,数组环形跳转
  • 8/10, Google Tag
  • (FB) Rearrange String k Distance Apart
  • Abstract Algebra
    • Chap1 -- Why Abstract Algebra ?
    • Chap2 -- Operations
    • Chap3 -- The Definition of Groups
Powered by GitBook
On this page
  • LRU Cache
  • 一句话描述这题的话,就是一个 “头 + 尾 dummy node 的双向链表” + “HashMap”.
  • Find Median from Data Stream
  • Sliding Window Maximum
  • 操作特点:
  • 要点:

Was this helpful?

常见数据结构设计

PreviousMatrix Inplace OperationsNext(G) Design / OOD 类算法题

Last updated 4 years ago

Was this helpful?

一道非常亲切熟悉的题,当初刷 lintcode 的时候非常喜欢这道。这道题因为细节很多,但是操作重复性高,所以非常适合先把流程写下来,然后用 helper 函数实现。

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

  • 先沟通各种 input/output

  • 思考流程

  • 把流程用 comments (另一种颜色 mark 笔写下来)

  • 把发现流程中重复率高的地方写成 function

  • 模块化填充,改错。

public class LRUCache {
    private class Node{
        int key;
        int value;
        Node prev;
        Node next;
        public Node(int key, int value){
            this.key = key;
            this.value = value;
        }
    }

    private int capacity;
    private HashMap<Integer, Node> map;
    // Head : Most recently used
    private Node dummyHead;
    // Tail : Least recently used
    private Node dummyTail;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.map = new HashMap<Integer, Node>();
        dummyHead = new Node(-1,-1);
        dummyTail = new Node(-1,-1);
        dummyHead.next = dummyTail;
        dummyTail.prev = dummyHead;
    }

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

        // Get current node
        Node node = map.get(key);
        // Disconnect it from the linkedlist
        node.prev.next = node.next;
        node.next.prev = node.prev;
        // Move it to the front
        moveToFront(node);
        // Return value
        return node.value;
    }

    public void set(int key, int value) {
        if(map.containsKey(key)){
            // Get current node
            Node node = map.get(key);
            // Disconnect it from the linkedlist
            node.prev.next = node.next;
            node.next.prev = node.prev;
            // Move it to the front
            moveToFront(node);
            // set value
            node.value = value;
        } else {
            if(map.size() >= capacity){
                // Remove LRU element and its key
                removeLRU();
            }
            // Create new node
            Node node = new Node(key, value);
            // Save its key in map
            map.put(key, node);
            // Move it to the front
            moveToFront(node);
        }
    }

    private void moveToFront(Node node){
        Node oldHead = dummyHead.next;

        // Connect node to dummyHead
        dummyHead.next = node;
        node.prev = dummyHead;

        // Connect node to oldHead
        node.next = oldHead;
        oldHead.prev = node;
    }

    private void removeLRU(){
        Node node = dummyTail.prev;
        // Disconnect node from linkedlist
        node.prev.next = node.next;
        node.next.prev = node.prev;
        // Set null of its pointers
        node.next = null;
        node.prev = null;
        // Remove its key from hashmap
        map.remove(node.key);
    }
}

也是非常经典的题。

  • new PriorityQueue<>(Collections.reverseOrder());

  • 把所有数字分成两个区间,一个区间为 <= median 的,存在 maxHeap 里;另一个为 >= median 的,存在 minHeap 里,这样我们每次在堆顶看到的都是 median candidates.

  • 因此在插入新元素的时候,如果发现其 <= maxHeap top,则说明它位于左区间,插入 max; 反之则插入 minHeap 所代表的右区间。

  • 每次新元素插入之后要保持平衡,堆顶的元素切换其实代表着区间分界线的转移。

public class MedianFinder {
    // max heap stores all elements smaller / equal to median
    PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(Collections.reverseOrder());
    // min heap stores all elements bigger / equal to median
    PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>();

    // Adds a number into the data structure.
    public void addNum(int num) {
        // Check heap top

        if(maxHeap.size() == 0 || num <= maxHeap.peek()){
            maxHeap.offer(num);
        } else {
            minHeap.offer(num);
        }

        // min/max heap's size would not differ more than one
        // Rebalance if necessary
        while(Math.abs(maxHeap.size() - minHeap.size()) > 1){
            if(maxHeap.size() > minHeap.size()){
                minHeap.offer(maxHeap.poll());
            } else {
                maxHeap.offer(minHeap.poll());
            }
        }
    }

    // Returns the median of current data stream
    public double findMedian() {
        int maxSize = maxHeap.size();
        int minSize = minHeap.size();

        if(maxSize == minSize) return (double) (maxHeap.peek() + minHeap.peek()) / 2;
        if(maxSize - 1 == minSize) return (double) maxHeap.peek();
        if(maxSize == minSize - 1) return (double) minHeap.peek();

        return 0;
    }
};

操作特点:

  • 只关心区域内的 max;

  • 只要同一区域内 max 一样,输出就一样,不在意元素的出现顺序,因此这题有别于 max/min stack 的保存元素顺序要求。

这题暴力循环可以 O(nk),hasheap 可以 O(n log k),deque 可以 O(n).

我们每一步要执行下面三个操作:

  • 添加当前元素 nums[i];

  • 删除指定元素 nums[i - k];

  • 找到当前 window max;

暴力解法中,我们可以直接维护一个 list,每次操作都进行扫描,这样的复杂度是:

  • Add O(1)

  • Remove O(k)

  • max O(k)

另一种选择是,维护一个 sorted list,这样的复杂度是:

  • Add O(k)

  • Remove O(k)

  • max O(1)

再观察题目特性可以发现,我们在维护 sorted list 上有很多可以取巧的地方:

  • 因为我们只关心每个 window 上的 max,因此没有必要把 window 内的所有元素都留着,在 Add 的时候可以直接把小于新元素的值都 pop 掉;

  • 而在 remove 上,对最终结果会产生影响的,只有我们 remove 的元素就是 max 的情形,因为其他无关元素在 add 的时候就被拿掉了;因此我们可以存一个元素的相对位置,以此来判断我们要删掉的元素是不是当前的 max.

  • 因为我们维护的是 sorted array ,max 依然是 O(1).

  • 由于每一个元素只会被 add 和 remove 一次,整个流程的均摊复杂度就是 O(1).

要点:

  • 维护一个可以双向操作的 sorted array.

  • Deque 里面要存 index,而不是值,不然会出现 [8,1,2,8...] 这种情况下,我们有可能会把刚加进去的 8 又给拿出来的 bug.

public class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums == null || nums.length == 0) return new int[0];

        int[] rst = new int[nums.length - k + 1];
        // Deque stores indexes of elements i, in descending order of nums[i]
        // First : smallet element in current window
        // Last : biggest element in current window
        Deque<Integer> deque = new LinkedList<>();

        for(int i = 0; i < nums.length; i++){
            while(!deque.isEmpty() && nums[i] > nums[deque.peekFirst()]){
                deque.pollFirst();
            }
            deque.offerFirst(i);
            // element to remove from sliding window
            if(i - k == deque.peekLast()) deque.pollLast();
            if(i - k + 1 >= 0) rst[i - k + 1] = nums[deque.peekLast()];
        }

        return rst;
    }
}

LRU Cache
Find Median from Data Stream
Sliding Window Maximum