如何答一道惊艳面试官的数组去重问题?

数组去重应该是面试 必考 问题之一。

虽然它是一道并不复杂的问题,但是也能看出面试者的 广度和深度 ,还有考虑问题的全面性。

实际开发中我们应该选择哪种方式数组去重,本文告诉你。

你以为的不一定你以为,面试官不只是让你去重一个数组,他想知道的有点多,包括你的思想。

当面试官问到时怎么回答?

首先:我知道多少种去重方式

双层 for 循环

functiondistinct(arr){for(leti=0, len=arr.length; i

思想: 双重 for 循环是比较笨拙的方法,它实现的原理很简单:先定义一个包含原始数组第一个元素的数组,然后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行比对,如果不重复则添加到新数组中,最后返回新数组;因为它的时间复杂度是 O(n^2) ,如果数组长度很大, 效率会很低 。

Array.filter() 加 indexOf

functiondistinct(a, b){letarr = a.concat(b);returnarr.filter((item, index)=>{returnarr.indexOf(item) === index    })}复制代码

思想: 利用indexOf检测元素在数组中第一次出现的位置是否和元素现在的位置相等,如果不等则说明该元素是重复元素

Array.sort() 加一行遍历冒泡(相邻元素去重)

functiondistinct(array){varres = [];varsortedArray =array.concat().sort();varseen;for(vari =0, len = sortedArray.length; i < len; i++) {// 如果是第一个元素或者相邻的元素不相同if(!i || seen !== sortedArray[i]) {            res.push(sortedArray[i])        }        seen = sortedArray[i];    }returnres;}复制代码

思想: 调用了数组的排序方法 sort() ,V8引擎 的 sort() 方法在数组长度小于等于10的情况下,会使用插入排序,大于10的情况下会使用快速排序(sort函数在我之前高阶函数那篇文章有详细讲解 【JS必知必会】高阶函数详解与实战 )。然后根据排序后的结果进行遍历及相邻元素比对(其实就是一行冒泡排序比较),如果相等则跳过该元素,直到遍历结束。

ES6 中的 Set 去重

functiondistinct(array){returnArray.from(newSet(array));}复制代码

甚至可以再简化下:

functionunique(array){return[...newSet(array)];}复制代码

还可以再简化下:

letunique =(a) =>[...newSet(a)]复制代码

思想: ES6 提供了新的数据结构 Set,Set 结构的一个特性就是成员值都是唯一的,没有重复的值。(同时请大家注意这个简化过程)

Object 键值对

functiondistinct(array){varobj = {};returnarray.filter(function(item, index, array){returnobj.hasOwnProperty(typeofitem + item) ?false: (obj[typeofitem + item] =true)    })}复制代码

这种方法是利用一个空的 Object 对象,我们把数组的值存成 Object 的 key 值,比如 Object[value1] = true ,在判断另一个值的时候,如果 Object[value2]存在的话,就说明该值是重复的,但是最后请注意这里 obj[typeof item + item] = true 没有直接使用 obj[item] ,是因为 因为 123 和 '123' 是不同的,直接使用前面的方法会判断为同一个值,因为 对象的键值只能是字符串 ,所以我们可以使用 typeof item + item 拼成字符串作为 key 值来避免这个问题。

然后:询问面试官具体场景

(如果你考虑的这些和你问的,面试官不以为然,可能自己都没想,随便让你数组去重,可能这个面试官也...)

性能考虑(是想要最快的速度查到数据吗?)

为了测试这些解法的性能,我写了一个测试模版,用来计算数组去重的耗时。 模版代码如下:

// distinct.jsletarr1 =Array.from(newArray(100000), (x, index)=>{returnindex})letarr2 =Array.from(newArray(50000), (x, index)=>{returnindex+index})letstart =newDate().getTime()console.log('开始数组去重')letarr = a.concat(b);functiondistinct(arr){// 数组去重}console.log('去重后的长度', distinct(arr).length)letend =newDate().getTime()console.log('耗时', end - start)复制代码

上面的多种数组去后,计算耗费时间

双重 for 循环 > Array.filter()加 indexOf > Array.sort() 加一行遍历冒泡 > ES6中的Set去重 > Object 键值对去重复

注意:这里只是本人测试的结果,具体情况可能与场景不同,比如排序过的数组直接去重,直接使用冒泡相邻比较性能可能更好。大家也可以自己尝试一下,有问题欢迎一起讨论指出。

兼容性与场景考虑(数组中是否包含对象,NaN等?)

我们要考虑这个数组中是否有null、undefined、NaN、对象如果二者都出现,上面的所有数组去重方法并不是都是适用哦,下面详细说一下。

先说一下 == 和 === 区别

=== 严格相等,会比较两个值的类型和值 == 抽象相等,比较时,会先进行类型转换,然后再比较值 想更详细了解转换过程的可以看这篇文章 js 中 == 和 === 的区别

说一下我说的几个类型的相等问题

letstr1 ='123';letstr2 =newString('123');console.log(str1 == str2);// trueconsole.log(str1 === str2);// falseconsole.log(null==null);// trueconsole.log(null===null);// trueconsole.log(undefined==undefined);// trueconsole.log(undefined===undefined);// trueconsole.log(NaN==NaN);// falseconsole.log(NaN===NaN);// falseconsole.log(/a/==/a/);// falseconsole.log(/a/===/a/);// falseconsole.log({} == {});// falseconsole.log({} === {});// false复制代码

几种去重函数针对带有特殊类型的对比

indexOf 与 Set 的一点说明:

上面代码中 console.log(NaN === NaN); // false , indexOf 底层使用的是 === 进行判断,所以使用 indexOf 查找不到 NaN 元素

// demo1vararr = [1,2,NaN];arr.indexOf(NaN);// -1复制代码

Set可以去重NaN类型, Set内部认为尽管 NaN === NaN 为 false,但是这两个元素是重复的。

// demo2functiondistinct(array){returnArray.from(newSet(array));}console.log(unique([NaN,NaN]))// [NaN]复制代码

具体去重比较

将这样一个数组按照上面的方法去重后的比较:

vararray = [1,1,'1','1',null,null,undefined,undefined,newString('1'),newString('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(vari =0; i

补充说明第三方库lodash

lodash 如何实现去重

简单说下 lodash 的 uniq 方法的源码实现。

这个方法的行为和使用 Set 进行去重的结果一致。

当数组长度大于等于 200 时,会创建 Set 并将 Set 转换为数组来进行去重(Set 不存在情况的实现不做分析)。当数组长度小于 200 时,会使用类似前面提到的 双重循环 的去重方案, 另外还会做 NaN 的去重 。

总结

面试时回答面试官的问题,除了你能把代码编出来运行出正确的结果,正确还包含对问题的独到见解,还需要考虑下面的问题:

优化

代码规范

容错性 其实如果是非常难的问题,对你的竞争对手来说,也是难的,关键在于你所表达出的解决问题的思路,甚至通过表达解题思路的方向,以及你考虑问题的全面性,其实不仅仅这一道简单面试题,算法题是如此。我是 koala ,今天这篇先写这么多,一起加油。

通过怎么样的学习,才能成为合格的WEB前端工程师?如果现在的你很迷茫,可以+web前端扣扣裙:965747894 免费网课在线学习以及问题解答、项目指导服务,配合强大的学习工具,带你完成九大实战项目,经历从零基础到专业前端工程师的完美蜕变。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,784评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,745评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,702评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,229评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,245评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,376评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,798评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,471评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,655评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,485评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,535评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,235评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,793评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,863评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,096评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,654评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,233评论 2 341

推荐阅读更多精彩内容