ARTS是什麼?
Algorithm:每週至少作一個leetcode的算法題;
Review:閱讀並點評至少一篇英文技術文章;
Tip:學習至少一個技術技巧;
Share:分享一篇有觀點和思考的技術文章。html
題目:
java
LC 239. Sliding Window Maximumgit
解答:
github
滑動窗口問題是面試的常考題;老樣子一步步來,先考慮最暴力的解法,就是循環遍歷尋找,這樣的話時間複雜度須要 ,其中 n 是數組的長度,k 窗子的長度。而後考慮能不能優化,很容易想到的是 sorted_map,也就是堆,這種數據結構對其中的元素的刪除跟增添操做都是 ,查找最值的操做時間複雜度是 , 若是用這種數據結構維護窗子裏面的數的話,那麼能夠將總體的時間複雜度降至 。進一步再想,能不能繼續優化呢?這裏其實有一個細節就是,在窗口滑動過程當中,新進來的數若是是最大值,那麼以前的數永遠不多是最大值,能夠將以前的數都踢走,若是以前有比新進來的數大的數,那就按普通隊列存儲就行,下面的生命週期永遠是最短的,這樣子的話咱們能夠考慮雙端隊列這麼一個數據結構,就是兩頭可出可進,數組裏面的全部元素最多被加入一次,被刪除一次,這樣看的話時間複雜度是 ,空間複雜度 , 從時間複雜度上看,毫無疑問這是最優的解法。代碼實現部分貼了兩種解法,一種使用 Java 裏面的 TreeMap,另一種是使用 ArrayDeque(從 LeetCode 上面顯示的運行時間比較,比用 LinkedList 實現的 Deque 快很多)。面試
實現參考代碼:算法
TreeMap 版本,時間複雜度:,空間複雜度: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 版本,時間複雜度:,空間複雜度:數組
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);
}
複製代碼
在其餘網友分享的 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
會是 00001111
,h & (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() 函數來根據實際狀況優化本身的設計
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
複製代碼
這周分享以前寫過的關於揹包問題的算法題目,給出了一些我的的理解和題目的分析,歡迎一塊兒討論和分享