做者簡介:koala,專一完整的 Node.js 技術棧分享,從 JavaScript 到 Node.js,再到後端數據庫,祝您成爲優秀的高級 Node.js 工程師。【程序員成長指北】做者,Github 博客開源項目 github.com/koala-codin…javascript
function distinct(arr) {
for (let i=0, len=arr.length; i<len; i++) {
for (let j=i+1; j<len; j++) {
if (arr[i] == arr[j]) {
arr.splice(j, 1);
// splice 會改變數組長度,因此要將數組長度 len 和下標 j 減一
len--;
j--;
}
}
}
return arr;
}
複製代碼
思想: 雙重 for 循環是比較笨拙的方法,它實現的原理很簡單:先定義一個包含原始數組第一個元素的數組,而後遍歷原始數組,將原始數組中的每一個元素與新數組中的每一個元素進行比對,若是不重複則添加到新數組中,最後返回新數組;由於它的時間複雜度是O(n^2)
,若是數組長度很大,效率會很低
。html
function distinct(a, b) {
let arr = a.concat(b);
return arr.filter((item, index)=> {
return arr.indexOf(item) === index
})
}
複製代碼
思想: 利用indexOf檢測元素在數組中第一次出現的位置是否和元素如今的位置相等,若是不等則說明該元素是重複元素java
function distinct(array) {
var res = [];
var sortedArray = array.concat().sort();
var seen;
for (var i = 0, len = sortedArray.length; i < len; i++) {
// 若是是第一個元素或者相鄰的元素不相同
if (!i || seen !== sortedArray[i]) {
res.push(sortedArray[i])
}
seen = sortedArray[i];
}
return res;
}
複製代碼
思想: 調用了數組的排序方法 sort()
,V8引擎 的 sort() 方法在數組長度小於等於10的狀況下,會使用插入排序,大於10的狀況下會使用快速排序(sort函數在我以前高階函數那篇文章有詳細講解【JS必知必會】高階函數詳解與實戰)。而後根據排序後的結果進行遍歷及相鄰元素比對(其實就是一行冒泡排序比較),若是相等則跳過該元素,直到遍歷結束。git
function distinct(array) {
return Array.from(new Set(array));
}
複製代碼
甚至能夠再簡化下:程序員
function unique(array) {
return [...new Set(array)];
}
複製代碼
還能夠再簡化下:github
let unique = (a) => [...new Set(a)]
複製代碼
思想: ES6 提供了新的數據結構 Set,Set 結構的一個特性就是成員值都是惟一的,沒有重複的值。(同時請你們注意這個簡化過程)面試
function distinct(array) {
var obj = {};
return array.filter(function(item, index, array){
return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
})
}
複製代碼
這種方法是利用一個空的 Object 對象,咱們把數組的值存成 Object 的 key 值,好比 Object[value1] = true
,在判斷另外一個值的時候,若是 Object[value2]存在的話,就說明該值是重複的,可是最後請注意這裏obj[typeof item + item] = true
沒有直接使用obj[item]
,是由於 由於 123 和 '123' 是不一樣的,直接使用前面的方法會判斷爲同一個值,由於對象的鍵值只能是字符串
,因此咱們可使用 typeof item + item
拼成字符串做爲 key 值來避免這個問題。算法
(若是你考慮的這些和你問的,面試官不覺得然,可能本身都沒想,隨便讓你數組去重,可能這個面試官也...)數據庫
爲了測試這些解法的性能,我寫了一個測試模版,用來計算數組去重的耗時。 模版代碼以下:c#
// distinct.js
let arr1 = Array.from(new Array(100000), (x, index)=>{
return index
})
let arr2 = Array.from(new Array(50000), (x, index)=>{
return index+index
})
let start = new Date().getTime()
console.log('開始數組去重')
let arr = a.concat(b);
function distinct(arr) {
// 數組去重
}
console.log('去重後的長度', distinct(arr).length)
let end = new Date().getTime()
console.log('耗時', end - start)
複製代碼
上面的多種數組去後,計算耗費時間
雙重 for 循環 > Array.filter()加 indexOf > Array.sort() 加一行遍歷冒泡 > Object 鍵值對去重複 > ES6中的Set去重
注意:這裏只是本人測試的結果,具體狀況可能與場景不一樣,好比排序過的數組直接去重,直接使用冒泡相鄰比較性能可能更好。你們也能夠本身嘗試一下,有問題歡迎一塊兒討論指出。
咱們要考慮這個數組中是否有null、undefined、NaN、對象若是兩者都出現,上面的全部數組去重方法並非都是適用哦,下面詳細說一下。
===
嚴格相等,會比較兩個值的類型和值 ==
抽象相等,比較時,會先進行類型轉換,而後再比較值 想更詳細瞭解轉換過程的能夠看這篇文章js 中 == 和 === 的區別
let str1 = '123';
let str2 = new String('123');
console.log(str1 == str2); // true
console.log(str1 === str2); // false
console.log(null == null); // true
console.log(null === null); // true
console.log(undefined == undefined); // true
console.log(undefined === undefined); // true
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
console.log(/a/ == /a/); // false
console.log(/a/ === /a/); // false
console.log({} == {}); // false
console.log({} === {}); // false
複製代碼
indexOf 與 Set 的一點說明:
上面代碼中console.log(NaN === NaN); // false
, indexOf 底層使用的是 === 進行判斷,因此使用 indexOf 查找不到 NaN 元素
// demo1
var arr = [1, 2, NaN];
arr.indexOf(NaN); // -1
複製代碼
Set能夠去重NaN類型, Set內部認爲儘管 NaN === NaN 爲 false,可是這兩個元素是重複的。
// demo2
function distinct(array) {
return Array.from(new Set(array));
}
console.log(unique([NaN, NaN])) // [NaN]
複製代碼
將這樣一個數組按照上面的方法去重後的比較:
var array = [1, 1, '1', '1', null, null, undefined, undefined, new String('1'), new String('1'), /a/, /a/, NaN, NaN];
複製代碼
方法 | 結果 | 說明 |
---|---|---|
雙層 for 循環 | [1, "1", null, undefined, String, String, /a/, /a/, NaN, NaN] | 對象和 NaN 不去重 |
Array.sort()加一行遍歷冒泡 | [/a/, /a/, "1", 1, String, 1, String, NaN, NaN, null, undefined] | 對象和 NaN 不去重 數字 1 也不去重 |
Array.filter()加 indexOf | [1, "1", null, undefined, String, String, /a/, /a/] | 對象不去重 NaN 會被忽略掉 |
Object 鍵值對去重 | [1, "1", null, undefined, String, /a/, NaN] | 所有去重 |
ES6中的Set去重 | [1, "1", null, undefined, String, String, /a/, /a/, NaN] | 對象不去重 NaN 去重 |
雖說對於 V8 引擎,內存考慮已經顯得不那麼重要了,並且真的數據量很大的時候,通常去重在後臺處理了。儘管如此,咱們也不能放過任何一個能夠證實本身優秀的
,仍是考慮一下,嘿嘿。
以上的全部數組去重方式,應該 Object 對象去重複的方式是時間複雜度是最低的,除了一次遍歷時間複雜度爲O(n)
後,查找到重複數據的時間複雜度是O(1)
,相似散列表,你們也可使用 ES6 中的 Map 嘗試實現一下。
可是對象去重複的空間複雜度是最高的,由於開闢了一個對象,其餘的幾種方式都沒有開闢新的空間,從外表看來,更深刻的源碼有待探究,這裏只是要說明你們在回答的時候也能夠考慮到時間複雜度
還有空間複雜度
。
另外補充一個誤區,有的小夥伴會認爲 Array.filter()
加 indexOf
這種方式時間複雜度爲 O(n)
,其實不是這樣,我以爲也是O(n^2)
。由於 indexOf
函數,源碼其實它也是進行 for 循環遍歷的。具體實現以下
String.prototype.indexOf = function(s) {
for (var i = 0; i < this.length - s.length; i++) {
if (this.charAt(i) === s.charAt(0) &&
this.substring(i, s.length) === s) {
return i;
}
}
return -1;
};
複製代碼
簡單說下 lodash
的 uniq
方法的源碼實現。
這個方法的行爲和使用 Set 進行去重的結果一致。
當數組長度大於等於 200
時,會建立 Set
並將 Set
轉換爲數組來進行去重(Set 不存在狀況的實現不作分析)。當數組長度小於 200
時,會使用相似前面提到的 雙重循環 的去重方案,另外還會作 NaN 的去重。
面試時回答面試官的問題,除了你能把代碼編出來運行出正確的結果,正確還包含對問題的獨到看法,還須要考慮下面的問題: