js面試題之數組去重對比


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

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

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

但是,從其他方面來說,這個代碼是否就真的是很好的呢?作為一個曾經的半吊子acmer,效率恐怕是我最在意的東西了。那我們就來看下效率吧。

以下所有實驗均基於nodejs環境。

 

首先,我們需要隨機生成一個足夠大的數組。這里就來個10萬吧。然后跨度是1-10000。(為什么要提跨度,這個后面再說),然后記錄下開始時間,並使用上面的代碼跑一下,最后記錄結束時間,並相減就可以了。

下面是代碼

 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的數組,效率是多少呢?

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

這個效率,恩。。很一般。

我們看下代碼也不難得出這個結論。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);

 

 

效率對比就非常明顯了。

 

下面來說一下之前留下的坑:

1、為什么要用跨度呢,因為實驗中發現(一開始並沒有細想),如果數組跨度越下,那么第一種方法的速度就越快,但是其他兩種,尤其是第二種的變化幅度就沒那么劇烈,這是為什么呢?

其實是因為,indexOf的機制造成的。indexOf的實現是從你規定的位置開始往后找,找到第一個就停下來。所以很明顯的,如果跨度越小,那么出現重復數字的幾率就越高,那么就越有可能很快有返回結果,所以跨度越小就會越接近n的效率。

反觀其他兩個方法,不管你跨度多少,影響的都是數組的長度,那么影響就只在遍歷一遍數組的效率這方面,所以就很小。

2、其實第一種方法的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);

 

 

第三種:

優點:速度相對較快,受其他因素影響不大,適用范圍也相對較廣。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM