面试题3:数组中重复的数字

数据结构中围绕数组、字符串、链表、树、栈以及队列,会有大量的面试题。

此题是关于数组的问题,另外,根据不同的具体要求,此题有多种考法,具体代码实现也会不同。

牛客网上的题目 :

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

《剑指offer》上根据不同的具体要求,出了两个题目,其中第一个题目与牛客网上的相同。如下:

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

简单理解就是判断某个长度为n的数组中,是否有重复的数字。
根据牛客网上的提示代码:

 public boolean duplicate(int numbers[],int length,int [] duplication)

是要求 判断值返回,并把第一个重复的数字放到duplication[0]中。

我个人的做法是:

 1 public static boolean duplicate(int numbers[],int length,int [] duplication) {
 2         if(numbers==null||numbers.length==0){
 3             duplication[0] = -1; 
 4             return false;
 5         }
 6         ArrayList<Integer> arr = new ArrayList<Integer>();
 7         //Arrays.sort(numbers);
 8         //System.out.println(Arrays.toString(numbers));
 9         for(int i:numbers){
10             arr.add(i);
11         }
12         //Iterator<Integer> it = arr.iterator();
13         /*while(it.hasNext()){
14             System.out.print(it.next()+" ");
15         }*/
16         for(int i:numbers){
17             if(arr.contains(i)){
18                 
19                 //it.remove();
20                 int j = arr.indexOf(i);
21                 arr.remove(j);//根据下标 去除对应的下标
22                 //System.out.println(arr);
23                 if(arr.contains(i)){
24                     duplication[0]=i;
25                     System.out.println(i);
26                     return true;
27                 }
28             }
29         }
30         return false;
31     }

主要思路是:①先判断是否输入的数组,是否为空或者长度为0,如果是,直接返回false。如果不是,继续执行。

②定义一个ArrayList arr,用一个for循环将numbers中的值,add(i)到arr中。

③再for循环numbers数组,用arr.contains(i)判断是否包含对应的数字,如果包含,删除对应数字,再进行arr.contains(i),如果还包含,就说明此数字重复,返回true即可。

虽然结果是对的,但是由于另外创建了ArrayList,导致空间复杂度为O(n)。

根据《剑指offer》上提出的思路,实现下面代码,使得空间复杂度为O(1):

 1 /*
 2      * 交换数组内的数字顺序
 3      */
 4     public static boolean duplicate2(int numbers[],int length,int [] duplication) {
 5         if(numbers==null||numbers.length==0){
 6             duplication[0] = -1; 
 7             return false;
 8         }
 9         for(int i = 0;i<length;i++){
10             while(numbers[i]!=i)
11             {
12                 if(numbers[i]==numbers[numbers[i]]){
13                     duplication[0] = numbers[i];
14                     System.out.println(duplication[0]);
15                     return true;
16                 }
17                 int temp = numbers[i];
18                 numbers[i] = numbers[temp];
19                 numbers[temp] = temp;
20             }
21         }
22         return false;
23     }

主要思路是:①先判断输入的数组是否为空,或者数组长度为0。

②让数组内的数字和数组的下标之间产生关联,即如果数组内的数字与当前数字的数组下标不相等,就将数组内的数字,交换到对应数字下标的位置,例如数组内numbers[0]的值是2,2不等于0,则将2与numbers[2]的交换。

③在数组内的数字与当前数字的数组下标不相等的情况下,如果出现,当前数字与即将交换位置的数字相等,则说明当前数字重复,返回true。例如,如果numbers[0]为2,2不等于0,则判断2是否等于numbers[2],如果numbers[2]为2,则说明2是重复的,结束。

另外,如果将题目改一下,限制一下数组里数字的范围,就是《剑指offer》上这道题的第二个题目:

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

根据书上给出的思路,自己实现的代码:

 1 public static boolean duplicate3(int[] numbers, int length, int[] duplication) {
 2         if(numbers==null||numbers.length==0){
 3             duplication[0] = -1; 
 4             return false;
 5         }
 6         //int start = 1,end = length-1;
 7         int start = 1,end = length-1;
 8         while(end >= start){
 9             //int middle = ((end-start)>>1)+start;
10             int middle = (end-start)/2 + start;
11             int count = countRange(numbers,length,start,middle);
12             if(start==end){
13                 if(count>1){
14                     duplication[0] = start;
15                     System.out.println(start);
16                     return true;
17                 }else{
18                     duplication[0] = -1; 
19                     System.out.println(start);
20                     return false;
21                 }
22             }
23             
24             if(count>middle){
25                 end = middle;
26             }else{
27                 start = middle+1;
28             }
29         }
30         return false;
31     }
32     public static int countRange(int[] numbers, int length, int start, int end) {
33         int count=0;
34         for(int i=0;i<length;i++){
35             if(numbers[i]>=start && numbers[i]<=end){
36                 count++;
37             }
38         }
39         return count;
40         
41     }

用的是类似于二分法的思路,例如在1-3的范围中,如果小于3和大于1的数字的个数count,大于3,说明这个范围的数字是有重复的,然后进一步缩小范围,然后再进行计数,直到start==end的
范围中,出现count>1的,即是重复的数。

这种算法也是有缺陷的,比如在1-2范围内,有两个2,此时不能确定是每个数字各出现一次,还是某个数字出现两次。

 

posted @ 2018-04-13 17:38  oweiziqiango  阅读(179)  评论(0编辑  收藏  举报