js面試題之數組去重對比

 最近看一些面試題,不少都提到了數組去重,用的最多的不外乎就是下面這個例子node

arr.filter(function(value,index,arr){
  return arr.indexOf(value,index+1) === -1
})面試

若是忽略其餘因素,只從代碼簡潔度和易讀性來講,這個代碼確實是很好的,也確實體現了對js的掌握程度。數組

可是,從其餘方面來講,這個代碼是否就真的是很好的呢?做爲一個曾經的半吊子acmer,效率恐怕是我最在乎的東西了。那咱們就來看下效率吧。瀏覽器

如下全部實驗均基於nodejs環境。dom

 

首先,咱們須要隨機生成一個足夠大的數組。這裏就來個10萬吧。而後跨度是1-10000。(爲何要提跨度,這個後面再說),而後記錄下開始時間,並使用上面的代碼跑一下,最後記錄結束時間,並相減就能夠了。測試

下面是代碼spa

 1 var arr = [];
 2 //生成隨機數組
 3 for (var i = 100000; i >= 0; i--) {
 4     arr.push(Math.ceil(Math.random()*10000));
 5 }
 6 
 7 //開始時間
 8 var now1 = new Date();
 9 var arr1 = arr.filter(function(value,index,arr){
10     return arr.indexOf(value,index+1) === -1
11 })
12 // console.log(arr1.sort(function(a,b){
13 //     return a-b;
14 // }));
15 //結束時間
16 var now2 = new Date();
17 console.log(now2-now1);

那麼10萬個跨度是10000的數組,效率是多少呢?code

執行結果: 2000毫秒左右,3000毫秒之內(其餘瀏覽器環境測試結果可能不盡相同)對象

這個效率,恩。。很通常。blog

咱們看下代碼也不可貴出這個結論。filter效率爲n,indexOf效率爲n,又是嵌套的,那麼效率就是n²了。(這裏有小問題,下面會解答)

 

接下來是我本身想的兩個去重的方法,對應不一樣的場景吧。

一:若是數組裏面的值都是數字的話。那麼,咱們可使用數字對應下標的方法來進行去重。好比說,個人數組是arr1=[1,3,3,3,5,6],那麼我就新開一個數組arr2,arr2的值就是[1,,1,,1,1]

也就是說,只要是arr1中出現過的值,我就在arr2中找到對應的下標,而且賦值爲1。

 1 var now1 = new Date();
 2 
 3 //中間數組,用來記錄arr出現過的值
 4 var tmp = [];
 5 arr.forEach(function(value){
 6     tmp[value] = 1;
 7 });
 8 
 9 var arr2 = [];
10 tmp.filter(function(value,index){
11     if(value === 1){
12         arr2.push(index);
13     }
14 })
15 
16 var now2 = new Date();
17 console.log(now2-now1);

 

執行結果: 5毫秒左右,10毫秒之內

那麼這個結果就很明顯,效率之間的差距不是通常的大。。。

從代碼也能夠看出來,都只有一層循環,那麼效率就是O(n)了。仍是很是快的。

 

二:使用sort進行排序。並使用filter過濾數組便可

1 var now1 = new Date();
2 //使用sort從小到大排序並過濾掉先後相同的元素
3 var arr3 = arr.sort(function(a,b){
4     return a-b;
5 }).filter(function(value,index){
6     return arr[index] !== arr[index-1];
7 });
8 var now2 = new Date();
9 console.log(now2-now1);

執行結果: 80毫秒左右,100毫秒之內

能夠看出來這個結果也還算能夠(對比第一種)

不難看出來,node中sort的效率應該在nlogn(-。-廢話麼這不是)

而且2次遍歷數組的消耗基本能夠忽略不計(從第二種能夠看出來)

那麼爲何既然比第二個的效率低,又沒第一個簡潔,爲何要用這種方法呢,第一是能夠擴寬思路,第二是由於這種方法的適用範圍比第二種要高。

好比說,我要去重的數組不是數字,而是字符。那麼第二種方法就沒法使用(可使用對象來實現,不過不知道對象遍歷的效率高不高,留做之後測試)。而且, 第一種方法若是其中有一個數字爲10E,那麼空間消耗就很大(js中不肯定,大部分語言是這樣)。由於要開一個10E的數組。。。之前比賽的時候用c開個1000萬的數組就超了。

 

三個方法放一次看下:

 1 var arr = [];
 2 //生成隨機數組
 3 for (var i = 100000; i >= 0; i--) {
 4     arr.push(Math.ceil(Math.random()*10000));
 5 }
 6 
 7 //開始時間
 8 var now1 = new Date();
 9 var arr1 = arr.filter(function(value,index,arr){
10     return arr.indexOf(value,index+1) === -1
11 })
12 
13 //結束時間
14 var now2 = new Date();
15 console.log(now2-now1);
16 
17 console.log("------------------------");
18 
19 now1 = new Date();
20 
21 //中間數組,用來記錄arr出現過的值
22 var tmp = [];
23 arr.forEach(function(value){
24     tmp[value] = 1;
25 });
26 
27 var arr2 = [];
28 tmp.filter(function(value,index){
29     if(value === 1){
30         arr2.push(index);
31     }
32 })
33 
34 now2 = new Date();
35 console.log(now2-now1);
36 
37 console.log("------------------------");
38 
39 now1 = new Date();
40 //使用sort從小到大排序並過濾掉先後相同的元素
41 arr3 = arr.sort(function(a,b){
42     return a-b;
43 }).filter(function(value,index){
44     return arr[index] !== arr[index-1];
45 });
46 //過濾數組
47 now2 = new Date();
48 console.log(now2-now1);

 

 

效率對比就很是明顯了。

 

下面來講一下以前留下的坑:

一、爲何要用跨度呢,由於實驗中發現(一開始並無細想),若是數組跨度越下,那麼第一種方法的速度就越快,可是其餘兩種,尤爲是第二種的變化幅度就沒那麼劇烈,這是爲何呢?

實際上是由於,indexOf的機制形成的。indexOf的實現是從你規定的位置開始日後找,找到第一個就停下來。因此很明顯的,若是跨度越小,那麼出現重複數字的概率就越高,那麼就越有可能很快有返回結果,因此跨度越小就會越接近n的效率。

反觀其餘兩個方法,無論你跨度多少,影響的都是數組的長度,那麼影響就只在遍歷一遍數組的效率這方面,因此就很小。

二、其實第一種方法的2000毫秒左右的效率是基因而隨機數組的狀況下,那麼若是咱們把數組改爲順序數組,也就是沒有重複的數組呢?

咱們來試驗一下

var arr = [];
//生成順序數組
for (var i = 100000; i >= 0; i--) {
    arr.push(i);
}

看到了吧,這纔是真正的n²的效率,以前的2000毫秒和這個仍是有常數級別的區別的。

咱們還發現,第三種方法的速度也變久了。將近翻了一倍,這個的話應該是sort內部實現的問題,我也不知道node內部sort使用的是什麼排序,不過排序方法通常都是對順序或者倒序和隨機之間有必定的差距,這個很正常。

 

實驗作完了,咱們來總結一下:

第一種:

優勢:簡潔,明瞭,必定程度上能夠看出對js的掌握程度,適用範圍很廣,而且不會改變數組元素的相對位置(分爲對後去重和對前去重兩種)。

缺點:效率很是慢,尤爲是在重複數很是少的狀況下。

第二種:

優勢:速度很是快,受其餘因素影響小(若是數量少,跨度大的話,也就是稀疏數組,反而會比其餘的慢)。

缺點:適用範圍比較小,只能適合數字數組,而且數組不能過大。若是爲其餘數組,可使用對象做爲數組。遍歷使用for in 便可,效率和第三種差距不大。代碼以下,可自行實驗。

 1 var now1 = new Date();
 2 
 3 //中間對象
 4 var tmp = {};
 5 arr.forEach(function(value){
 6     tmp[value+'we'] = 1;
 7 })
 8 
 9 var arr3 = [];
10 for(index in tmp){
11     if(tmp[index] === 1){
12         arr3.push(index);
13     }
14 }
15 
16 var now2 = new Date();
17 console.log(now2-now1);

 

 

第三種:

優勢:速度相對較快,受其餘因素影響不大,適用範圍也相對較廣。

缺點:中軌終於,適用範圍不如第一種大,速度不如第三種快。會受其餘因素必定的影響。

相關文章
相關標籤/搜索