Arts 第三週(4/1 ~ 4/7)

ARTS是什麼?
Algorithm:每週至少作一個leetcode的算法題;
Review:閱讀並點評至少一篇英文技術文章;
Tip:學習至少一個技術技巧;
Share:分享一篇有觀點和思考的技術文章。html

Algorithm

題目
java

LC 239. Sliding Window Maximumgit

解答
github

滑動窗口問題是面試的常考題;老樣子一步步來,先考慮最暴力的解法,就是循環遍歷尋找,這樣的話時間複雜度須要 O(k * n),其中 n 是數組的長度,k 窗子的長度。而後考慮能不能優化,很容易想到的是 sorted_map,也就是堆,這種數據結構對其中的元素的刪除跟增添操做都是 O(\log n),查找最值的操做時間複雜度是 O(1), 若是用這種數據結構維護窗子裏面的數的話,那麼能夠將總體的時間複雜度降至 O(n\log k)。進一步再想,能不能繼續優化呢?這裏其實有一個細節就是,在窗口滑動過程當中,新進來的數若是是最大值,那麼以前的數永遠不多是最大值,能夠將以前的數都踢走,若是以前有比新進來的數大的數,那就按普通隊列存儲就行,下面的生命週期永遠是最短的,這樣子的話咱們能夠考慮雙端隊列這麼一個數據結構,就是兩頭可出可進,數組裏面的全部元素最多被加入一次,被刪除一次,這樣看的話時間複雜度是 O(n),空間複雜度 O(k), 從時間複雜度上看,毫無疑問這是最優的解法。代碼實現部分貼了兩種解法,一種使用 Java 裏面的 TreeMap,另一種是使用 ArrayDeque(從 LeetCode 上面顯示的運行時間比較,比用 LinkedList 實現的 Deque 快很多)。面試

實現參考代碼算法

TreeMap 版本,時間複雜度:O(n \log k),空間複雜度:O(k)api

public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums == null || nums.length == 0 || nums.length < k) {
            return new int[0];
        }
        
        int[] result = new int[nums.length - k + 1];
        
        TreeMap<Integer, Integer> tm = new TreeMap<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer a, Integer b) {
                return b - a;
            }
        });
        
        for (int i = 0; i < nums.length; ++i) {
            if (i >= k) {
                if (tm.get(nums[i - k]) == 1) {
                    tm.remove(nums[i - k]);
                } else {
                    tm.put(nums[i - k], tm.get(nums[i - k]) - 1);
                }
            }
            
            if (tm.containsKey(nums[i])) {
                tm.put(nums[i], tm.get(nums[i]) + 1);
            } else {
                tm.put(nums[i], 1);
            }
            
            if (i + 1 >= k) {
                result[i + 1 - k] = tm.firstKey();
            }
        }
        
        return result;
    }
複製代碼

Deque 版本,時間複雜度:O(n),空間複雜度:O(k)數組

public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums == null || nums.length == 0 || nums.length < k) {
            return new int[0];
        }
        
        ArrayDeque<Integer> dq = new ArrayDeque<>();
        
        int[] result = new int[nums.length - k + 1];
        
        for (int i = 0; i < nums.length; ++i) {
            if ((i >= k) && (dq.peekLast() == i - k)) {
                dq.pollLast();
            }
            
            addIntoDeque(i, dq, nums);
            
            if (i + 1 >= k) {
                result[i + 1 - k] = nums[dq.peekLast()];
            }
        }
        
        return result;
    }
    
    private void addIntoDeque(int index, ArrayDeque<Integer> dq, int[] nums) {
        if (dq.isEmpty()) {
            dq.offerFirst(index);
            return;
        }
        
        while ((!dq.isEmpty()) && (nums[dq.peekFirst()] <= nums[index])) {
            dq.pollFirst();
        }
        
        dq.offerFirst(index);
    }
複製代碼

Review

在其餘網友分享的 ARTS 中看到了一篇講 Java HashMap 具體實現的文章,How does a HashMap work in JAVA 看完後彌補了以前認知上的不足,在這裏把本身新的認知分享一下,固然仍是推薦有時間的朋友能夠看一看,或許你會有不同的發現bash

  • get(),remove(),還有 put() 均會返回 value,注意是 value,而不是 keymarkdown

  • Java 8 裏面的 hash 函數以下:

    // the "rehash" function in JAVA 8 that directly takes the key
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    // the function that returns the index from the rehashed hash
    static int indexFor(int h, int length) {
        return h & (length-1);
    }
    複製代碼

    這裏仍是要說一下的是,hash() 這個函數實際上是幹了三件事情

    1. 經過 key 自帶的 hashCode() 函數獲得 key 的哈希值
      2. rehashes,就是給 hashcode 重新定位,防止由於 collision 而致使的分佈不均的問題
      3. rehashes 的結果做爲 HashMap 裏面數組的訪問下標參考值(不是最終的下標)返回
    複製代碼

    經過這兩個簡短的函數,有一個認識就是,HashMap 裏面的內部數組大小最好是 2 的指數冪,不然會形成數組利用率過低的狀況,例如 array 大小是 17 的話,那麼 length - 1 就會是 00010000,進行 h & (length - 1) 操做後時只會生成兩個不一樣的 index,一個是 00010000,另外一個是 00000000,反過來,array 大小是 16 的話,length - 1 會是 00001111h & (length - 1) 操做後會有概率生成 16 個不一樣的index。

  • Java 提供了幾個 HashMap 的構造函數,可參照 HashMap Doc

    public HashMap() {...}
    public HashMap(int initialCapacity) {...}
    public HashMap(int initialCapacity, float loadFactor) {...}
    public HashMap(Map<? extends K,? extends V> m) {...}
    複製代碼

    使用者能夠經過 initialCapacity 指定 HashMap 內部初始數組的大小,數組會自動擴容,條件由 loadFactor 來控制,閾值是 (capacity of the inner array) * loadFactor,超過這個值,數組會擴爲以前的兩倍,若是不指定這兩個值,initialCapacity 的默認大小是 16,loadFactor 默認大小是 0.75;有一點須要注意的是,擴容其實就是把舊的數組中的全部的元素用以前的那兩個函數(hash, indexFor)再過一遍,映射到新的數組中,以前在同一個 index 下的元素在映射事後位置可能會不一樣

  • 多線程的狀況下,最好是用 ConcurrentHashMap,而不是 HashTable,由於相比HashTable 整個操做都加鎖,而 ConcurrentHashMap 鎖的範圍只會限制在數組的單個 index 上,就是隻是在訪問數組同一個 index 的狀況下會被加鎖,這樣速度會比 HashTable 快不少

  • Java 8 把數組每一個 index 下的數據結構在特定的狀況下變成了紅黑樹,對比以前的鏈表,查詢效率要快一點,可是時間和空間每每是一個 trader-off 的關係,紅黑樹的實現空間要比鏈表大一些

    // in the way of linked list
    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;
        …
    }
    
    // in the way of red-black tree
    static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        final int hash; // inherited from Node<K,V>
        final K key; // inherited from Node<K,V>
        V value; // inherited from Node<K,V>
        Node<K,V> next; // inherited from Node<K,V>
        Entry<K,V> before, after;// inherited from LinkedHashMap.Entry<K,V>
        TreeNode<K,V> parent;
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;
        boolean red;
        … 
    }
    複製代碼
  • HashMap 最核心的部分實際上是 hash function,這個函數很大程度上決定了裏面的元素能不能分佈均勻,這固然得經過具體問題來進行設計,使用者在設計本身的 Object 是,能夠實現裏面自帶的 hashCode() 函數來根據實際狀況優化本身的設計


Tip

Markdown 你們都很熟悉,最近寫 ARTS 纔開始用 Markdown,用好了確實很方便,包括 Markdown 實際上是兼容 HTML 的,HTML 的一些 TAG 也能夠根據需求去使用,這裏列了些以前沒有想到過,可是很實用的 Markdown 技巧:

  • 嵌套引用
    >>

  • 嵌套列表

    1. 第一層
         + 1-1
         + 1-2
           + 1-2-1
           + 1-2-2
    複製代碼

    無序列表和有序列表能夠隨意互相嵌套

  • 意大利斜體
    *content*

  • Markdown 不支持指定圖片的顯示大小,可是能夠經過 <img /> 標籤來指定,例如
    <img src="https://avatars2.githubusercontent.com/u/3265208?v=3&s=100" alt="GitHub" title="GitHub,Social Coding" width="50" height="50" />

  • 刪除線
    ~~content~~

  • 表格,使用 | 來分隔不一樣的單元格,使用 - 來分隔表頭和其它行:

    name | age
    ---- | ---
    Yu   | 25
    che  | 23
    複製代碼
    • :--- 表示左對齊
    • :--: 表示居中對其
    • ---: 表明右對齊

    另外就是斜體,加粗,連接之類的語法標記也能夠在表格中使用

  • 任務標記

    - [ ] task1
    - [x] task2    標記任務已完成
        - [ ] task2-1   子任務
        - [ ] task2-2
    - [ ] task3
    複製代碼

Share

這周分享以前寫過的關於揹包問題的算法題目,給出了一些我的的理解和題目的分析,歡迎一塊兒討論和分享

常見揹包問題解法分析

相關文章
相關標籤/搜索