题目描述:
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出应该是2或3。
代码如下:
解法一:
// 从第一个数开始,每一个数依次与其后面的数字进行比较,若相等,则将其赋值到duplication[0],并返回 true 。
function duplicate(numbers, duplication){
//这里要特别注意:找到任意重复的一个值并赋值到duplication[0]
//函数返回True/False
let len = numbers.length;
for(let i=0; i<len-1; i++){
for(let j=i+1; j<len; j++){
if(numbers[i]===numbers[j]){
duplication[0] = numbers[i];
return true;
}
}
}
return false;
}
解法二(冒泡排序):
// 首先将数组进行排序,然后再从头到尾扫描一次排序后的数组即可。
function duplicate(numbers, duplication){
//这里要特别注意:找到任意重复的一个值并赋值到duplication[0]
//函数返回True/False
let len = numbers.length;
// 冒泡排序:平均时间复杂度O(n^2)
for(let i=0; i<len-1; i++){
for(let j=0; j<len-i-1; j++){
if(numbers[j]>numbers[j+1]){
let temp = numbers[j];
numbers[j] = numbers[j+1];
numbers[j+1] = temp;
}
}
}
for(let i=0; i<len-1; i++){
if(numbers[i]===numbers[i+1]){
duplication[0] = numbers[i];
return true;
}
}
return false;
}
解法二(快速排序):
function duplicate(numbers, duplication){
//这里要特别注意:找到任意重复的一个值并赋值到duplication[0]
//函数返回True/False
let len = numbers.length;
// 快速排序:平均时间复杂度O(nlogn)
numbers = quickSort(numbers);
for(let i=0; i<len-1; i++){
if(numbers[i]===numbers[i+1]){
duplication[0] = numbers[i];
return true;
}
}
return false;
}
function quickSort(n){
if(n.length<=1){
return n;
}
let midIndex = Math.floor(n.length / 2);
let mid = n.splice(midIndex,1)[0];
console.log(n); // 如果原来的 n = [4,7,2,9,3], 则现在的 n =[ 4, 7, 9, 3 ]
console.log(n.splice(midIndex,1)); // [2]
console.log(mid); // 2
let left = [];
let right = [];
for(let i=0; i<n.length; i++){
if(n[i] < mid){
left.push(n[i])
} else {
right.push(n[i])
}
}
return quickSort(left).concat([mid],quickSort(right))
}
解法三:
// 将数组中的第i个数字(从第0个数开始,和数组的下角标保持一致)与其下角标比较。
// 若相等,比较下一个;否则把它交换到下角标与它相同的位置上。
// 如果那个相同位置上原本的值与它相等,说明出现重复数字,此时则将其赋值到duplication[0],并返回 true 。
function duplicate(numbers, duplication){
//这里要特别注意:找到任意重复的一个值并赋值到duplication[0]
//函数返回True/False
let len = numbers.length;
if(len<=0){
return false;
}
for(let i=0; i<len; i++){
if(numbers[i]<0 || numbers[i]>len-1){
return false;
}
}
for(let i=0; i<len; i++){
while(numbers[i]!==i){
if(numbers[i] === numbers[numbers[i]]){
duplication[0] = numbers[i];
return true;
}
let temp = numbers[i];
numbers[i] = numbers[temp];
numbers[temp] = temp;
}
}
return false;
}
解法四:
哈希表。从头到尾扫描数组的每一个数字,每扫描到一个数字,都可以用O(1)的时间来判断哈希表里是否已经包含了该数字。如果哈希表里还没有这个数字,就把它加入哈希表。如果哈希表已经存在该数字,就找到一个重复的数字。
解法比较:
第一种方法:时间复杂度为O(n^2),空间复杂度为O(1)。
代码中有一个两重循环,因此,总的时间复杂度为O(n^2)。另外,所有的操作都只是将输入数组进行比较,没有额外分配内存,因此空间复杂度为O(1)。
第二种方法(冒泡排序):时间复杂度为O(n^2),空间复杂度为O(1)。
第二种方法(快速排序):时间复杂度为O(nlogn),空间复杂度为O(logn)。
第三种方法:时间复杂度为O(n),空间复杂度为O(1)。
代码中尽管有一个两重循环,但每个数字最多只要交换两次就能找到属于它自己的位置,因此,总的时间复杂度为O(n)。另外,所有的操作都是在输入数组上进行的,不需要额外分配内存,因此空间复杂度为O(1)。
第四种方法:时间复杂度为O(n),空间复杂度为O(n)。
拓展练习:
题目描述:不修改数组找出重复的数字。
在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。例如,如果输入长度为8 的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或3。
代码如下:
function getDuplication(numbers){
if(numbers.length<=0){
return -1;
}
let start = 1;
let end = numbers.length-1;
while(end>=start){
let middle = ((end-start)>>1)+start;
let count = countRange(numbers,start,middle);
if(end===start){
if(count>1){
return start;
} else
break;
}
if(count>middle-start+1){
end = middle;
} else {
start = middle + 1;
}
}
return -1;
}
function countRange(numbers,start,end){
if(numbers.length<=0){
return -1;
}
let count = 0;
for(let i=0; i<numbers.length; i++){
if(numbers[i]>=start && numbers[i]<=end){
count++;
}
}
return count;
}
// 测试
console.log(getDuplication([2,3,5,4,3,2,6,7]))
上述代码按照二分查找的思路,如果输入长度为n的数组,那么函数countRange将被调用O(logn)次,每次需要O(n)的时间,因此总的时间复杂度为O(nlogn),空间复杂度为O(1)。
思考:
在不同的功能要求(找出任意一个重复的数字、找出所有重复的数字)或者性能要求(时间效率优先、空间效率优先)的条件下,应该选择哪种算法?
END