原文地址:https://segmentfault.com/a/1190000015923301
問題由來
遇到一道面試題:找到數組中第一個非重復的數。
[ 1, 1, 2, 2, 3, 4, 4, 5 ]
第一個非重復的數為 3
最簡單的想法就是兩層 for
循環遍歷數組,這樣的時間復雜度是 O(n^2)
。而更高效的方式,是使用hash Map
,可將時間復雜降為O(n)
。
其實這個題目可以衍生出三個類似的問題:
- 數組去重
- 找到數組中重復的數
- 找到數組中第一個非重復的數
我准備用ES6
中的 Map
數據結構來解決這三個問題,在這之前有必要先梳理下Map的主要知識點。
Map基礎梳理
JavaScript 的對象(Object),本質上是鍵值對的集合(Hash 結構),但是傳統上只能用字符串當作鍵。這給它的使用帶來了很大的限制。為了解決這個問題,ES6 提供了 Map
數據結構。它類似於對象,也是鍵值對的集合,但是“鍵”的范圍不限於字符串,各種類型的值(包括對象)都可以當作鍵。也就是說,Object
結構提供了“字符串—值”的對應,Map
結構提供了“值—值”的對應,是一種更完善的 Hash 結構實現。
例如Map構造函數接受一個數組作為其參數:
const map = new Map([ [1, '張三'], [2, '李四'] ]); // 0:{1 => "張三"} // 1:{2 => "李四"}
Map
實例的屬性和操作方法:
size
:返回成員總數set(key, value)
:添加新的鍵值get(key)
:讀取鍵對應的值has(key)
:是否有某個鍵delete(key)
:刪除某個鍵clear()
:清空
Map
實例的遍歷方法:
keys()
:返回鍵名的遍歷器。values()
:返回鍵值的遍歷器。entries()
:返回鍵值對的遍歷器。forEach()
:遍歷 Map 的所有成員。
下面來通過代碼解決三個問題:
數組去重
去重前:[ 1, 1, 2, 2, 3, 4, 4, 5 ]
去重后:[ 1, 2, 3, 4, 5 ]
主要思路:創建一個空Map
,遍歷原始數組,把數組的每一個元素作為key
存到Map中,因為Map
中不會出現相同的key
值,所以最終得到的Map
中的所有key
值就是去重后的結果。
function arrayNonRepeatfy(arr) { let hashMap = new Map(); let result = new Array(); // 數組用於返回結果 for (let i = 0; i < arr.length; i++) { if(hashMap.has(arr[i])) { // 判斷 hashMap 中是否已有該 key 值 hashMap.set(arr[i], true); // 后面的true 代表該 key 值在原始數組中重復了,false反之 } else { // 如果 hashMap 中沒有該 key 值,添加 hashMap.set(arr[i], false); result.push(arr[i]); } } return result; } let arr = [1, 1, 1, 2, 3, 3, 4, 5, 5, "a", "b", "a"]; console.log(arrayNonRepeatfy(arr)); // [ 1, 2, 3, 4, 5, 'a', 'b' ]
上面最終產生的Map
不僅可以達到去重的效果,而且對每一元素的重復性都做了標注,這樣想找到找到數組中重復的數就很方便了:
console.log(hashMap); /* 0:{1 => true} {key: 1, value: true} 1:{2 => false} {key: 2, value: false} 2:{3 => true} {key: 3, value: true} 3:{4 => false} {key: 4, value: false} 4:{5 => true} {key: 5, value: true} 5:{"a" => true} {key: "a", value: true} 6:{"b" => false} {key: "b", value: false} */
找到數組中重復的數
[ 1, 1, 2, 2, 3, 4, 4, 5 ]
[ 1, 2, 4 ]
接上一節末尾,既然hashMap
中記錄了每一個元素的重復情況,找到重復的數就很簡單了,遍歷最終得到的hashMap
,值為true
對應的鍵就是重復的數:
function findRepeatNumInArray(arr) { let hashMap = new Map(); let result = new Array(); for (let i = 0; i < arr.length; i++) { hashMap.set(arr[i], hashMap.has(arr[i])) } // 得到 hashMap 后,對其進行遍歷,值為 true,對應的鍵就是重復的數 for(let [key, value] of hashMap.entries()) { if(value === true) { result.push(key); } } return result; } let arr = [1, 1, 1, 2, 3, 3, 4, 5, 5, "a", "b", "a"]; console.log(findRepeatNumInArray(arr));
找到數組中第一個非重復的數
[ 1, 1, 2, 2, 3, 4, 4, 5 ]
3
代碼與上一節的差不多,遍歷最終得到的hashMap
,第一個值為false
對應的鍵就是第一個非重復數字:
function findFirstNonRepeat(arr) { let hashMap = new Map(); for (let i = 0; i < arr.length; i++) { hashMap.set(arr[i], hashMap.has(arr[i])) } // 找到第一個值為 false 的,就代表第一個非重復數,return 就好了 for(let [key, value] of hashMap.entries()) { if(value === false) { return key; } } return "全部重復"; } let arr = [1, 1, 1, 2, 3, 3, 4, 5, 5, "a", "b", "a"]; console.log(findFirstNonRepeat(arr));
總結,三類問題的核心其實就是:利用 Map
存儲每一個數字的重復情況。