一、题目
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
样例1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出: 2 或 3
限制:
2 <= n <= 100000
二、题解
废话不多说了,直接开始说方法
1. 使用set:循环遍历这个数组nums,直接用一个set来存储已经出现过的数据,用set中的has方法判断数字是否存在于set中,存在则返回该数字,不存在则将其用set中的add方法将其存进set.
1.2 使用set2:和方法1类似,也是用set,但是使用set中add前后的size值来判断(如果add之后size没变化,说明添加的数字set中已有,也就是重复数字,则返回该数字即可),会简单一些,思路是没问题的,本地样没问题,leetcode上一直报错,不知道为啥(这里是利用了set中不会添加重复值的性质)。
2.排序法:先对这个数组排序,然后分别判断这个数组的两个相邻元素是否相等,存在相等则是存在重复的数字.
3.原地交换法:利用“在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内”这个条件做文章,判断nums[i]是否等于i,如果等于则continue,然后判断num[nums[i]]和nums[i]是否相等,相等则代表存在相同的元素,这个时候return nums[i]即可,不等于则将nums[nums[i]]和nums[i]置换一下(这一步的目的就是为了使得nums[i]=i的情况),
这方法看起来可能是一头雾水(我一开始也是这样的),所以我们来代入数据详细看一下:
比如说有一个数组为[2,3,1,0,2,5,3]
那么的它索引值和对应的值的关系就为:
索引 | 0 1 2 3 4 5 6 |
---|---|
数组的值 | 2 3 1 0 2 5 3 |
所以根据这个算法,我们先判断0是否等于nums[0],显然0不等于2,
然后我们判断nums[nums[i]]是否等于nums[i],也就是nums[nums[0]]是否等于nums[0],也就是nums[2]是否等于2,显然1不等于2,所以我们要将nums[nums[i]]和nums[i]转换一下,所以转换之后,那么的它索引值和对应的值的关系就为:
索引 | 0 1 2 3 4 5 6 |
---|---|
数组的值 | 1 3 2 0 2 5 3 |
所以经过几次上述转换我们可以得到:
索引 | 0 1 2 3 4 5 6 |
---|---|
数组的值 | 0 1 2 3 2 5 3 |
这时候我们发现nums[4] == nums[nums[4]](也就是nums[i]=nums[nums[i]]) 都等于2,所以这个时候就可以判断是有了重复的数字了,直接返回nums[4]即可,也就可以得出重复数字为2
3.1.辅助数组法:.和3类似,同样利用了题目的条件,但是这个方法更好理解,直接用一个大小也为n的全0数组arr来表示nums[i]值的个数,每次循环的时候都arr[nums[i]]++,当arr[nums[i]]>1时,就代表出现了多个nums[i],也就是说明有了重复数字,直接返回nums[i]即可!
三、代码(js)
// 1.遍历这个数组,用一个set,判断数字是否存在于set中,存在则返回该数字,不存在则将其存进set
/**
* @param {number[]} nums
* @return {number}
*/
let findRepeatNumber = function(nums) {
let repetitionSet = new Set();
//遍历nums数组
for(let i = 0,len=nums.length; i < len; i++) {
if(repetitionSet.has(nums[i])) return nums[i];
repetitionSet.add(nums[i]);
}
return -1;
};
// // 1.2 这种之间判断set.add的思路感觉能行!!!但是有bug!!!!!!!之后再看看
/**
* @param {number[]} nums
* @return {number}
*/
let repetitionSet = new Set();
let findRepeatNumber = function(nums) {
//遍历nums数组
for(let i = 0,len=nums.length; i < len; i++) {
//如果add之后的set和原set size值相同,则代表有重复数字加进来了
// 这里是利用了set中不会添加重复值的性质
if( repetitionSet.size===repetitionSet.add(nums[i]).size)
{
return nums[i];
}
}
return -1;
};
//2.将这个数组排序,如果判断两个数组是否相等
/**
* @param {number[]} nums
* @return {number}
*/
let findRepeatNumber = function(nums) {
//对nums先进行排序,然后找相邻的数字是否相同
nums.sort()
for(let i = 0,len = nums.length;i<len;i++)
{
if(nums[i]==nums[i+1])
{
return nums[i]
}
}
return -1
};
//3.利用全题目的条件,将索引和函数值关联起来
/**
* @param {number[]} nums
* @return {number}
*/
let findRepeatNumber = function(nums) {
for(let i = 0,len = nums.length;i<len;i++)
{
if(nums[i]==i)
continue;
if(nums[i]==nums[nums[i]])
return nums[i];
//这里有个细节!!!!!不能如下这样写,因为第一行的nums[i]和第三行的nums[i]不是一个值!!!
// let t = nums[i];
// nums[i] = nums[nums[i]];
// nums[nums[i]] = t;
let t = nums[i];
nums[i] = nums[t];
nums[t] = t;
}
return -1
};
//3.1利用全题目的条件,将索引和函数值关联起来 相比于3 一个是在原数组上操作,一个多了一个辅助数组!
/**
* @param {number[]} nums
* @return {number}
*/
let findRepeatNumber = function(nums) {
let arr = new Array(nums.length).fill(0);
for(let i = 0,len = nums.length;i<len;i++)
{
arr[nums[i]]++;
if(arr[nums[i]]>1){
return nums[i];
break;
}
}
return -1
};
四、拓展思考
这里就具体讲下方法3的代码交换nums[i]和nums[nums[i]]时为什么不能写成:
let t = nums[i];
nums[i] = nums[nums[i]];
nums[nums[i]] = t;
具体来看的话就是:在第二行的时候nums[i]已经被nums[nums[i]]给覆盖了,所以第三行的nums[nums[i]]已经不是我们所想的nums[nums[i]]了,而是nums[nums[nums[i]]]了,所以第三行我们必须要用t,因为t在第一行被赋值为nums[i]后就没有改变!!!所以可以写成:
let t = nums[i];
nums[i] = nums[nums[i]];
nums[t] = t;
为了美观,所以我们把第二行的nums[i]也换成t了,所以我们最终写成:
let t = nums[i];
nums[i] = nums[t];
nums[t] = t;
写博客真的蛮累的,希望自己能坚持下去吧,加油!
题目来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/sh…