遇到一道面试题:找到数组中第一个非重复的数。面试
[ 1, 1, 2, 2, 3, 4, 4, 5 ]
第一个非重复的数为 3
最简单的想法就是两层 for
循环遍历数组,这样的时间复杂度是 O(n^2)
。而更高效的方式,是使用hash Map
,可将时间复杂降为O(n)
。数组
其实这个题目能够衍生出三个相似的问题:数据结构
我准备用ES6
中的 Map
数据结构来解决这三个问题,在这以前有必要先梳理下Map的主要知识点。函数
JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),可是传统上只能用字符串看成键。这给它的使用带来了很大的限制。为了解决这个问题,ES6 提供了 Map
数据结构。它相似于对象,也是键值对的集合,可是“键”的范围不限于字符串,各类类型的值(包括对象)均可以看成键。也就是说,Object
结构提供了“字符串—值”的对应,Map
结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。code
例如Map构造函数接受一个数组做为其参数:对象
const map = new Map([ [1, '张三'], [2, '李四'] ]); // 0:{1 => "张三"} // 1:{2 => "李四"}
Map
实例的属性和操做方法:ip
size
:返回成员总数set(key, value)
:添加新的键值get(key)
:读取键对应的值has(key)
:是否有某个键delete(key)
:删除某个键clear()
:清空Map
实例的遍历方法:字符串
keys()
:返回键名的遍历器。values()
:返回键值的遍历器。entries()
:返回键值对的遍历器。forEach()
:遍历 Map 的全部成员。下面来经过代码解决三个问题:get
去重前:[ 1, 1, 2, 2, 3, 4, 4, 5 ]
去重后:[ 1, 2, 3, 4, 5 ]
主要思路:建立一个空Map
,遍历原始数组,把数组的每个元素做为key
存到Map中,由于Map
中不会出现相同的key
值,因此最终获得的Map
中的全部key
值就是去重后的结果。hash
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
存储每个数字的重复状况。