题目描述
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。


思路1:逐个扫描,每个数都扫描一轮。时间复杂度是O(n^2)

public class Solution {
    // 返回任意重复的一个值,赋值duplication[0],这是题目要求
    public boolean duplicate(int numbers[],int length,int [] duplication) {
       for (int i = 0; i < length; i++) {
          for (int j = i + 1; j < length; j++) {
             if (numbers[j] == numbers[i]) {
                 duplication[0] = numbers[i];
                 return true;
            }
        }
    }
    return false;
    }
}

思路2:利用排序,再从排序好的数组中找,排序好后只用扫一轮就可以,排序时间复杂度一般能优化到O(nlogn)

import java.util.Arrays;
public class Solution {

    public boolean duplicate(int numbers[],int length,int [] duplication) {
        if(numbers == null || length == 0){
            return false;
        }
        Arrays.sort(numbers);
        for(int i=0;i<length-1;i++){
            if(numbers[i] == numbers[i+1]){
                duplication[0] = numbers[i];
                return true;
            }
        }
        return false;
    }
}

思路3:利用哈希表或辅助数组,空间换时间,空间复杂度是O(n),时间复杂度O(1)。当哈希表添加元素相同时,说明重复了(或者使用Hashmap记录次数,最后次数大于1的就是重复的)。自然,不用哈希表,可以用一个辅助数组来记录对应元素的角标,也是可以的(同样也可以用数组来记录次数)

import java.util.HashSet;
import java.util.Set;
public class Solution {
    public boolean duplicate(int numbers[],int length,int [] duplication) {
        Set<Integer> set = new HashSet<>();
        for(int i =0 ;i<length;i++){
            if(set.contains(numbers[i])){
                duplication[0] = numbers[i];
                return true;
            }else{
                set.add(numbers[i]);
            }
        }
        return false;
    }
}
//使用辅助数组的写法
public class Solution {
    public boolean duplicate(int numbers[],int length,int [] duplication) {
        int[] arr = new int[length];
        for(int j = 0;j<length;j++){
            arr[j] = -1;  //辅助数组赋初值,只要不在1到n-1即可,否则在后续判断时会出错
        }
        for(int i =0 ;i<length;i++){
            if(arr[numbers[i]] == numbers[i]){
                duplication[0] = numbers[i];
                return true;
            }else{
                arr[numbers[i]] = numbers[i];
            }
        }
        return false;
    }
}
//显然用来记录次数的辅助数组,不用赋初值  只要次数超过1就可以返回了,++的条件时次数为零
public class Solution {
    public boolean duplicate(int numbers[],int length,int [] duplication) {
        int[] arr = new int [length];
        for(int i = 0; i < length; i++){
            if(arr[numbers[i]] == 0){
                arr[numbers[i]] ++;
            }else{
                duplication[0] = numbers[i];
                return true;
            }
        }
        return false;
    }
}

思路4:这不是一个普通的具有重复数字的数组,利用条件--长度为n,数的范围1到n-1,因此如果每个数字都不重复时,排完序的数组中的元素值和该元素的索引相同,而一旦有重复的数,那就会出现,元素和角标不相同的情况,基于这个条件,构建思路4,也叫重排数组(其实和哈希表想法相似,当同一个位置元素出现多次时,说明重复了)这个想法需要不断交换对应的元素。
比如{4,1,3,1,4} ,将索引为0的4移动到索引为4的元素交换,让4归位,但是移动时发现索引为4的元素已经是4了,就说明出现了两次。

public class Solution {
        public boolean duplicate(int numbers[],int length,int [] duplication) {
        if(numbers == null || length <= 0) {
            return false;
        }

        for(int i = 0; i < length; i++) {
            while(numbers[i] != i) {
                if(numbers[i] == numbers[numbers[i]]) {
                    duplication[0] = numbers[i];
                    return true;
                }
                int temp = numbers[i];
                numbers[i] = numbers[temp];
                numbers[temp] = temp;
            }
        }      
        return false;
    }
}

很显然,思路4打乱了原数组的顺序,但是空间复杂度较低


:在剑指offer书中,本题还有一个附加题,那就是不允许打乱数组顺序,并且题目改为了长度为n+1,所有数字是1到n的范围。其余条件不变。显然之前的辅助数组的方法是可以的,如果辅助数组也不让用了。
书给出的解答方式是二分法:把1到n的数字从中间数字m分成两部分,若前一半1到m的数字数目超过m个,说明重复数字在前一半区间,否则,在后半区间m+1到n。每次在区间中都一分为二,直到找到重复数字。
整个时间复杂度是O(nlogn) 空间复杂度O(1).