LeetCode 刷題筆記

Borris發表於2019-09-26

從 Amazon OA 高頻題開始

總結每一道做過的題目,先總結一下思路,然後記錄從中獲取的新知識。題目順序不是按照序號順序的,而是題目在真實面試中出現的頻率排序。由於之前已經做過一些了,所以那些題目暫時還沒有更新上去。等重新做的時候加上吧。目前的目標是每天爭取記錄兩題在這個博文裡面。

146. LRU Cache

解法一

這是一個比較偷懶的解法。
這道題可以使用 LinkedHashMap 的特性來完成,因為 LinkedHashMap 內建特有的方法 protected boolean removeEldestEntry(Map.Entry eldest)。put 和 putAll 將呼叫這個方法,移除 LinkedHashMap 中最舊的那個元素,也就是這道題所要求的。

protected boolean removeEldestEntry(Map.Entry eldest) {
    return size() > MAX_ENTRIES;
}

本題中我們只需將 MAX_ENTRIES 替換為我們所要求的 capacity 即可。其他的 put 和 get 方法可以直接呼叫 LinkedHashMap 內建的方法。
實現程式碼:

class LRUCache {
    private int capacity;
    private LinkedHashMap<Integer, Integer> cache;
    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.cache = new LinkedHashMap<Integer, Integer>(capacity, 0.75f, true) {
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return size() > capacity;
            }
        };
    }

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

    public void put(int key, int value) {
        cache.put(key, value);
    }
}
/*
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */
解法二

利用構建 HashMap 和 DoubleLinkedList。每次 HashMap 更新一堆鍵值對,雙向連結串列也要相應更新, 把更新的值新增到頭結點,而最早新增的結點自然來到了連結串列的末尾。

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

    private int capacity, count;
    private Map<Integer, Node> map;
    private Node head, tail;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.count = 0;
        map = new HashMap<Integer, Node>();
        // 定義頭結點和尾節點
        head = new Node();
        tail = new Node();
        head.next = tail;
        tail.prev = head;
    }

    public int get(int key) {
        Node n = map.get(key);
        if (null == n) return -1;
        update(n);
        return n.value;
    }

    public void put(int key, int value) {
        Node n = map.get(key);
        if (null == n) {
            n = new Node(key, value); // 沒有這個值,直接新建就行
            map.put(key, n );
            add(n);
            ++count;
        }
        else {
            n.value = value; // 用新值替換
            update(n);
        }
        if (count > capacity) {
            Node toDel = tail.prev;
            remove(toDel);
            map.remove(toDel.key);
            --count;
        }
    }

    private void update(Node node) {
        remove(node);
        add(node); // 將 node 加到頭結點之後
    }

    private void add(Node node) {
        Node after = head.next;
        head.next = node; // 頭結點後是 node
        node.prev = head; // node 前是 head;
        node.next = after; // node 後是 after;
        after.prev = node; // after 前是 node;
    }

    // Double Linked List remove a node
    private void remove(Node node) {
        // Get the previous node and the after node. 
        Node before = node.prev, after = node.next;
        // assign the next pointer of before node pointing to after; and the after node previous pointer points to before thus caneeling the connection of the node.
        before.next = after;
        after.prev = before;
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */
  • Takeaway
    • 查閱了 LinkedHashMap 在題中的定義方式,瞭解到 LinkedHashMap 初始預設容量為16,預設載入因子為0.75。關於載入因子,如果實際容量達到了載入因子乘以設定的容量,那麼容量就將翻倍。如 16x0.75=12, 那麼實際容量到達了12,設定容量會達到 16x2 = 32。
    • 雙向連結串列(Double LinkedList) 構建中,add 和 remove 方法都是透過改變節點指標指向來實現再增刪的。初始化雙向連結串列時,注意要把頭結點的 next 指向尾結點,尾節點的 prev 指向頭結點。所有連結串列也要有直接前驅和直接後繼的定義。

42. Trapping Rain Water

此題求積水量,可以按列來求。根據木桶效應,每一列積水量等於左邊最大高度和右邊最大高度中較小的那個減去當前列的高度(當前列高需要小於最大高度的較小值)。這是求解此題的關鍵。

解法一: Brute Force

對每一列,都求出左邊做大高度和右邊最大高度。index = 0 沒有最大左邊界,index = height.length 沒有最大有邊界。

class Solution {
    public int trap(int[] height) {
        int sum = 0;
        for (int  i = 1; i < height.length; i++) {
            int maxLeft = 0;
            for (int j = i -1; j >= 0; j--) {
                if (height[j] > maxLeft) {
                    maxLeft = height[j];
                }
            }

            int maxRight = 0;
            for (int k = i + 1; k < height.length; k++) {
                if (height[k] > maxRight) {
                    maxRight = height[k];
                }
            }

            if (height[i] < Math.min(maxLeft, maxRight)) {
                sum += Math.min(maxLeft, maxRight) - height[i];
            }
        }

        return sum;
    }
}

時間複雜度:O(n^2), 空間複雜度 O(1)

解法二: 動態規劃

第二題中,對於每一列我們都進行了遍歷。其實可以使用兩個陣列將每一列左邊右邊最大的值儲存起來。這樣在求和時直接呼叫陣列中的值進行計算即可。

class Solution {
    public int trap(int[] height) {
        int[] maxLeft = new int[height.length];
        int[] maxRight = new int[height.length];

        for (int i = 1; i < height.length; i++) {
            // 這裡的 trick 是求第 i 列的左邊最大值時,只需將第 i-1 列的左邊最大值和它的高進行比較即可。
            maxLeft[i] = Math.max(maxLeft[i -1], height[i-1]); 
        }

        for (int i = height.length -2; i >= 0; i--) {
            maxRight[i] = Math.max(maxRight[i + 1], height[i+1]);
        }

        int sum = 0;
        for (int i = 0; i < height.length; i++) {
            if (height[i] < Math.min(maxLeft[i], maxRight[i])) {
                sum += Math.min(maxLeft[i], maxRight[i]) - height[i];
            }
        }

        return sum;
    }
}

時間複雜度:O(n), 空間複雜度 O(n)

解法三:雙指標

這個看了解答還是沒有太弄明白。明天再看一看吧。


200. Number of Islands

解法一: DFS

這道題如果能想到 DFS, 那麼問題就變得很簡單了。遍歷集合中所有的點,如果含有 ‘1’,那麼進行一次 DFS, 這個DFS的退出條件是:只要達到邊界,或者達到 '0',就退出。

class Solution {
    public int numIslands(char[][] grid) {
        // 處理空陣列,防止溢位
        if (grid == null || grid.length == 0) {
            return 0;
        }

        int row = grid.length;
        int col = grid[0].length;
        int numIsland = 0;
        for (int r = 0; r < row; r++) {
            for (int c = 0; c < col; c++) {
                if (grid[r][c] == '1') {
                    numIsland++; // 遇到 1 就進行一次 DFS, 那麼必然有一個島。
                    dfs(grid, r, c);
                }
            }
        }
        return numIsland;
    }

    private void dfs(char[][] grid, int r, int c) {
        int row = grid.length;
        int col = grid[0].length;
        // 邊界條件
        if (r < 0 || c < 0 || r >= row || c >= col || grid[r][c] == '0') {
            return;
        }

        grid[r][c] = '0'; // 標記查詢過的點為 0
        // 查詢四個方向
        dfs(grid, r + 1, c);
        dfs(grid, r - 1, c);
        dfs(grid, r, c - 1);
        dfs(grid, r, c + 1);
    }
}
解法二: BFS

稍後更新

  • Takeaway
    • 我們在本題中不需要用另外陣列來記錄 visited points,把訪問過的點直接置零即可,因為 DFS 的退出條件之一就是訪問到 '0' 這個點。
    • 需要注意的是題中 grid 是 char 型別的二維陣列,並非數字!如果給點賦值數字會造成 DFS 無法退出死迴圈。
819. Most Common Words

個人認為這是一道相當噁心的題目,因為有些 Test Case 實在是想不到。可能是經驗少了吧。
整個題目我的思路就是,把輸入的 paragraph 字串轉換為字串陣列形式,然後除了在 Banned Lists 裡面的字串外全部新增進 HashMap 中。如果字元為重複的,value (就是字元出現的次數) 加一; 如果該字串第一次出現,次數記為 1。
全部字串錄入完畢後,遍歷找出出現最多的字串。

class Solution {
    public String mostCommonWord(String paragraph, String[] banned) {
        /*
        Create a HashMap to store every word.
        */
        HashMap<String, Integer> map = new HashMap<>();
        String str = paragraph + ".";
        // 這裡的 trick 是,對於 “a, c, b,b,b, e” 這樣的 test case, 先替換逗號會造成三個 'b' 連在一起,所以先替換', ' (逗號及空格) 為 ',' 再替換 ',' 為空格。這樣一來語句的格式就正確了。不過居然有人會想出這樣的 test case 實在是太噁心了。。
        String[] newStr = paragraph.replaceAll("\\, ",",").replaceAll("\\,"," ").replaceAll("\\.","").replaceAll("\\!","").replaceAll("\\?","").replaceAll("\\'","").replaceAll("\\;","").split(" ");
        // 對字串陣列進行遍歷,如果在 banned list 中就不加入 HashMap 裡,不在的話就轉換成小寫後加入。
        // 如果是第一次加入,就令 value 為 1;否則 value 加一。
        for (String s : newStr) {
            boolean containBanned = false;
            for (String b : banned) {
                if (s.toLowerCase().equals(b)) {
                    containBanned = true;
                }
            }
            if (map.containsKey(s.toLowerCase()) && !containBanned) {
                int value = map.get(s.toLowerCase());
                map.put(s.toLowerCase(), value+1);
            }
            else if (!containBanned)
            {
                map.put(s.toLowerCase(), 1);
            }
        }
        // Loop for the HashMap to find out the most frequent word (Not in the banned String lists).
        Set<Map.Entry<String, Integer>> set = map.entrySet();
        int max = 0;
        String maxKey = "";
        for (Map.Entry<String, Integer> s : set) {
            if (s.getValue() >= max) {
                max = s.getValue();
                maxKey = s.getKey();
            }
        }
        return maxKey;
    }
}
  • Takeaway
    • 程式設計中出現了一些錯誤,比如忘記了所有的字串比較儲存都應該是在先轉換成小寫字元的前提下的。另外,Entry 是在 Map下的,所以得使用這種呼叫方式: Map.Entry。還有就是要時刻記住給變數賦值時兩邊型別應該一樣!!

發現如果所有題都寫在一篇裡篇幅就太長了,所以後面的題打算每一題都另起爐灶。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章