1、面试题03:找出数组中重复的数字。
题目描述
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
限制:
2 <= n <= 100000
本题LeetCode链接:点击跳转
2、解题方法
使用JavaScript语言
方法一:使用ES6中Set对象
思路:
Set自动忽略数组中的重复元素
PS:Set的使用参考:JavaScript中Set对象的介绍(附示例)
代码一:新旧数组对比得到重复的数字
/**
* @param {number[]} nums
* @return {number}
*/
/*
数组会溢出
const findRepeatNumber = function (nums) {
let newNums = [...new Set(nums)];
// let newNums = Array.from(new Set(nums));
for (let i = 0; i < nums.length; i++) {
if (nums[i] !== newNums[i]) {
return nums[i];
}
}
}
*/
// 一开始什么都没考虑就直接用了原数组的长度,后来反应过来 新数组的长度比原数组的短,
//所以用原数组的长度很有可能溢出。比如nums = [2, 3, 1, 0, 5, 3];
//下面的代码是使用新数组的长度,如果循环完还没有找到,并且原数组长度 > 新数组长度,
// 则原数组里没有遍历到的第一个数字就是重复的数字
var findRepeatNumber = function (nums) {
let newNums = [...new Set(nums)];
let len = newNums.length;
if (nums.length == len) return null;
// 这个也可以不做判断,因为题目说了数组中是存在有重复的数字的。
for (let i = 0; i < len; i++) {
if (nums[i] !== newNums[i]) {
return nums[i]
}
}
return nums[len];
}
代码二:利用Set的size属性
遍历数组中元素,放入Set对象中。若Set对象的长度未增加,则输出当前元素。
var findRepeatNumber = function (nums) {
let s = new Set(); //set为自动忽略重复元素
for (let i in nums) {
let current = s.size; //记录当前set的长度
s.add(nums[i]); //遍历数组,将元素添加到s中
if (s.size == current) {
//如果有重复的元素添加时,会自动被忽略掉,即s的长度不变
return nums[i];
}
}
};
//也可以把重复的放在一个数组里,可取到不同的重复数字
var findRepeatNumber = function (nums) {
let s = new Set(); //set为自动忽略重复元素
let j = 0, result = []; //定义一个数组来存放重复的数字
for (let i in nums) {
let current = s.size; //记录当前set的长度
s.add(nums[i]); //遍历数组,将元素添加到s中
if (s.size == current) {
result[j] = nums[i];
j++;
}
}
return result[0];
};
代码三:利用Set的has方法
遍历数组,判断Set里面是否存在该元素,存在则返回该元素;不存在则把该元素添加到Set中
(也可以新建一个数组,使用数组的includes方法,下面会提到)
var findRepeatNumber = function (nums) {
let s = new Set();
let len = nums.length;
for (let i = 0; i < len; i++) {
if (s.has(nums[i])) { //如果s存在该元素则返回
return nums[i];
}
else {
s.add(nums[i]); //如果不存在该元素则添加到s
}
}
};
方法二:利用obj对象的键值对形式
将数组中的元素变成对象的key,出现的次数变成value
代码一:把数组的元素和出现次数存入对象中
var findRepeatNumber = function (nums) {
let obj = {};
let res;
for (let num of nums) { // for...in 和 for...of 的区别
if (obj[num]) {
obj[num] = obj[num] + 1; //js对象属性obj.attr 和 obj[attr]的区别
return num;
} else {
obj[num] = 1;
}
}
};
顺手补上一个链接:js 对象属性通过点(.) 和 方括号 ( [] ) 的不同之处:obj.attr 和 obj[attr]
这里补上力扣上的一个热门答案:使用哈希表和原地哈希
代码二:循环也可以用数组的find方法代替
find() 方法返回通过测试(函数内判断)的数组的第一个元素的值。(ES6数组的新方法)
find() 方法为数组中的每个元素都调用一次函数执行:
当数组中的元素在测试条件时返回 true 时, find() 返回符合条件的元素,之后的值不会再调用执行函数。
如果没有符合条件的元素返回 undefined
注意: find() 对于空数组,函数是不会执行的。
注意: find() 并没有改变数组的原始值。
var findRepeatNumber = function (nums) {
let obj = {};
let result;
nums.find(num => { //num是数组里面的每一个元素
if (obj[num]) {
result= num;
return num;
}
obj[num] = 1;
})
return result;
};
方法三:使用ES6的Map对象
跟Set方法的代码三很相似,其思路又很像把数组变成obj对象的键值对。
Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。
Map对象的具体使用方法以及与Obj对象的区别可自行百度
var findRepeatNumber = function (nums) {
let len = nums.length;
let map = new Map();
for (let i = 0; i < len; i++) {
if (map.has(nums[i]))
return nums[i];
map.set(nums[i], true);
}
};
方法四:判断数组的下标
代码一:利用js数组方法indexOf和lastIndexOf,从前后两个顺序遍历,发现下标不同,则是有重复的
indexOf() 方法可返回数组中某个指定的元素位置。
该方法将从头到尾地检索数组,看它是否含有对应的元素。开始检索的位置在数组 start 处或数组的开头(没有指定 start 参数时)。如果找到一个 item,则返回 item 的第一次出现的位置。开始位置的索引为 0。
lastIndexOf() 方法可返回一个指定的元素在数组中最后出现的位置,从该字符串的后面向前查找。
该方法将从尾到头地检索数组中指定元素 item。开始检索的位置在数组的 start 处或数组的结尾(没有指定 start 参数时)。如果找到一个 item,则返回 item 从尾向前检索第一个次出现在数组的位置。数组的索引开始位置是从 0 开始的。
indexOf() 和 lastIndexOf() 如果要检索的元素没有出现,则该方法返回 -1。
var findRepeatNumber = function (nums) {
for (let i = 0; i < nums.length; i++) {
let result = nums[i];
// if (nums.indexOf(result) != nums.lastIndexOf(result)) {
if (i != nums.lastIndexOf(result)) {
return result;
}
}
return null;
}
代码二:新建数组,判断新数组是否存在重复的数字
(跟之前的Set方法,新建一个数组有相似的地方)
var findRepeatNumber = function (nums) {
let arr = [];
for (let i in nums) {
if(arr.includes(nums[i])){
// if(arr.indexOf(nums[i]) > -1){ //使用includes方法或者indexOf方法
return nums[i];
}
arr.push(nums[i]);
}
};
代码三:遍历数组,判断这个数字在数组的剩余内容中是否再次出现
var findRepeatNumber = function (nums) {
let len = nums.length;
for (let i = 0; i < len; i++) {
let result = nums[0];
// nums.splice(0, 1);
nums.shift();
if(nums.indexOf(result) > -1){
return result;
}
}
};
/* 错误代码
var findRepeatNumber = function (nums) {
for (let i in nums) {
let result = nums[i];
nums.splice(i, 1);
if(nums.indexOf(result) > -1){
return result;
}
}
};
*/
方法五:直接判断该数值是否重复
代码一:排序之后比较相邻的数字
var findRepeatNumber = function (nums) {
nums.sort((a, b) => a - b);
let len = nums.length - 1; //因为数组会溢出,所以要减一
for (let i = 0; i < len; i++) {
if (nums[i] === nums[i + 1]) return nums[i];
}
return -1;
}
代码二:双循环逐一比较数值
var findRepeatNumber = function (nums) {
let len = nums.length;
for (let i = 0; i < len; i++) {
for (let j = i + 1; j < len; j++) {
if (nums[i] === nums[j]) {
return nums[i];
}
}
}
}
3、总结
方法一:使用ES6的Set对象(size属性、has方法);
方法二:使用ES6的Map对象;
方法三:使用obj对象,把数组中的数字和出现次数存为键值对形式;
方法四:比较数字的下标,使用一些数组方法(lastIndexOf、indexOf、includes等);
方法五:直接比较数字,使用排序后比较相邻数组、双循环比较数字。
大概的时间复杂度排序:方法一二三 < 方法五的排序后比较 < 方法四、方法五双循环
js数组方法是有一定的时间复杂度的,具体参考JS - 数组方法的效率
——————————————————分割线————————————————
①以上代码部分来自于leetcode的代码解答,这里做一个汇总整理,方便自己记忆。
②牛客网上有很多面经,也有很多笔试题,对找工作非常有帮助。但是编程题上没办法只查看针对某一语言的代码解析(这里不是说代码,而是指评论解答)。leetcode的编程题非常对,也非常友好(可以只看某某一语言的解答,可以直接搜索某种解题方法)。牛客网的评论量很大,大神后面会有很多的留言什么的。leetcode上面除了大神的解答,好像就,没了。
③初次写博客,如有错误欢迎指出来。