51 数组中重复的数字

题目要求:在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

 1 public class Solution {
 2     // Parameters:
 3     //    numbers:     an array of integers
 4     //    length:      the length of array numbers
 5     //    duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
 6     //                  Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
 7     //    这里要特别注意~返回任意重复的一个,赋值duplication[0]
 8     // Return value:       true if the input is valid, and there are some duplications in the array number
 9     //                     otherwise false
10     public boolean duplicate(int numbers[],int length,int [] duplication) {
11     }
12 }

 思路一:最好的解法

此大法利用了哈希的特性,但不需要额外的存储空间。 因此时间复杂度为O(n),不需要额外空间!

因为列表总共有n个元素,所有元素可能取到的元素有0~n-1,共n种。如果不存在重复的数字,那么排序后数字i将会出现在下标为i的位置。现在让我们重新扫描数组,

  • 当扫描到下标为i的数字时,首先比较这个数字(记为m)与i是否相等:
  • 如果是,继续扫描下一个元素,
  • 如果不是,则再拿它与第m个数字比较:
    • 如果它和第m个数字相同,就找到了一个重复的元素;
    • 如果不同,就将m与第m个数字互换。接下来继续重头开始,重复换这个比较。
 1 public class Solution {
 2     public boolean duplicate(int numbers[],int length,int [] duplication) {
 3         //考虑特殊情况,并判断数组是否合法(每个数都在0~n-1之间)
 4         if(length<=0||numbers==null) return false;
 5         for(int i=0;i<length;i++){
 6             if(numbers[i]<=0||numbers[i]>length-1)
 7                 return false;
 8         }
 9         //关键代码来了
10         for(int i=0;i<length;i++){
11             while(numbers[i]!=i){
12                 if(numbers[i]==numbers[numbers[i]]){
13                     duplication[0] = numbers[i];
14                     return true;
15                 }
16                 //交换numbers[i]和numbers[numbers[i]]
17                 int temp = numbers[i];
18                 numbers[i] = numbers[temp];
19                 numbers[temp] = temp;
20             }
21         }
22         return false;
23     }
24 }

 居然能找到错误,开森

/* 一开始交换写错了写成了这:
int temp = numbers[i];
numbers[i] = numbers[numbers[i]];
numbers[numbers[i]] = temp;
于是就错了...
*/

 思路二:利用HashMap或者HashTable 

不建议

  1. 由于所有元素值是有范围的,因此可以用一个长度为n的数组,下标表示序列中的每一个值,下标对应的值表示该下标出现的次数。
  2. 只需扫描一次原序列,就统计出所有元素出现的次数;
  3. 再扫描一次哈希数组,找到一个出现次数大于1的值即可。

 这种方法时间复杂度和空间复杂度都为O(n)。

 1 public boolean duplicate(int array[],int length,int [] duplication) {
 2     if ( array==null ) return false;
 3 
 4     // 判断数组是否合法(每个数都在0~n-1之间)
 5     for ( int i=0; i<length; i++ ) {
 6         if ( array[i]<0 || array[i]>length-1 ) {
 7             return false;
 8         }
 9     }
10 
11     // key step
12     int[] hash = new int[length];
13     for( int i=0; i<length; i++ ){
14         hash[array[i]]++;
15     }
16     for(int i=0; i<length; i++){
17         if ( hash[i]>1 ) {
18             duplication[0] = i;
19             return true;
20         }
21     }
22     return false;
23 }

 另外一个相似的题目

在一个长度为n+1的数组里的所有数字都在1~n范围内,所以数字中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但是不能修改数组。例如,如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或3。(n+1个元素,n种可能的取值)

最优解法:避免使用O(n)的辅助空间。我们把取值空间[1,n]从中间的数字m分为两部分,前面一部分为1~m,后面一部分为m+1~n。如果数组中元素落在前面一部分的元素个数多于m个,那么数组中重复的元素一定落在前半区间;否则,数组中重复的元素一定落在后半区间。然后,我们可以继续将包含重复元素的区间一分为二,直到找到一个重复的元素。

猜你喜欢

转载自www.cnblogs.com/shareidea94/p/11237176.html