剑指offer--面试题3:数组中重复的数字

面试题3:数组中重复的数字
题目一:
在一个长度为n的数组里的所有数字都在0~n-1的范围内。数组中某些数字是重复的,但是不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3.
思路分析:
算法1:对数组进行排序,然后从头到尾扫描扫描排序后的数组。排序一个长度为n的数组需要O(nlogn)的时间。

bool duplicate(int numbers[], int length, int* duplication) {
        if(numbers==nullptr || length<=0){
            return false;
        }
        sort(numbers,numbers+length);// 对数组进行归并排序,复杂度为O(nlogn)
        for(int i=0; i<length-1; i++){
            if(numbers[i]==numbers[i+1]){
                *duplication=numbers[i];
                return true;
            }
        }
        return false;
    }

算法2:利用哈希表,从头到尾扫描数组的每一个元素,每扫描一个数字,都用O(1)的时间来判断哈希表里是否包含了该数字,如果哈希表里没有该数字,就把它加入哈希表,如果哈希表中已存在该数字,那么就找到了一个重复的数字。该算法的时间复杂度为O(n),代价是以一个空间为O(n)的哈希表。
代码:

bool duplicate(int numbers[], int length, int* duplication) {
        if(numbers==nullptr || length<=0){
            return false;
        }
        map<int,int> m ;
        for(int i=0; i<length; i++){
            m[numbers[i]]++;
        }
        for(int i=0; i<length; i++){
            if(m[i]>1){
                *duplication=i;
                return true;
            }
        }
        return false;
    }

算法3:进一步改进算法,使算法的空间复杂度为O(1)。注意到数组中的数字都在0~n-1的范围内,如果数组中没有重复的数字,那么当数组排序之后数字 i 将出现在下标为 i 的位置。由于数组中有重复的数字,有些位置可能存在多个数字,有的位置可能没有数字。现在我们重新排列这个数组,从头到尾依次扫描数组中的每个数字,当扫描到下标为 i 的数字时,首先比较这个数字(用m表示)是不是等于 i 。如果是,则接着扫描下一个数字; 如果不是,则再拿它和第m个数字进行比较。如果它和第m个数字相等,就找到了一个重复的数字(该数字在下标为m和 i 的位置都出现了);如果它和第m个数字不相等,就把第 i 个数字和第 m 个数字交换,把m放到属于它的位置。接下来重复这个比较、交换的过程,直到我们发现一个重复的数字。

bool duplicate(int numbers[], int length, int* duplication) {
        if(numbers==nullptr || length<=0){
            return false;
        }
        for(int i=0; i<length; ++i){
            if(numbers[i]>length-1 || numbers[i]<0){
                return false;
            }
        }
        for(int i=0; i<length; ++i){
            while(numbers[i]!=i){
                if(numbers[i] == numbers[numbers[i]]){
                    *duplication=numbers[i];
                    return true;
                }
                    //swap numbers[i] and numbers[numbers[i]]
                    int temp=numbers[i];
                    numbers[i]=numbers[temp];
                    numbers[temp]=temp;
            }
        }
         return false;
    }

上述代码中,找到的重复值通过参数duplication传给函数的调用者,而函数的返回值表示数组中是否有重复的数字。
虽然代码中有一个两重循环,但是每个数字最多交换两次就能找到自己的位置,因此总的时间复杂度是O(n),所有操作都在原数组上操作,不需重新分配内存,因此空间复杂度为O(1)。

题目二:
不修改数组找出重复数字
在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。例如,如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出数字应该是2或者3。
思路分析:
算法1:创建一个长度为n+1的辅助数组,然后逐一把原数组的每个元素复制到辅助数组,如果原数组中被复制的数字是m,则复制到辅助数组下标是m的位置,这样很容易发现哪个数字是重复的,但是需要O(n)的辅助空间。

bool duplicate(int numbers[], int length, int* duplication) {
        if(numbers==nullptr || length<=0){
            return false;
        }
        int *a=new int[length]; // 新建一个辅助数组a
        for(int i=0; i<length; i++){
            if(a[numbers[i]]==numbers[i]){
                *duplication=numbers[i];
                return true;
            }
            else{
            a[numbers[i]]=numbers[i];
            }
        }
        return false;
    }

算法2:若数组中没有重复数字,那么在1 ~ n的范围内,只有n个数字。由于数组里包含超过n个数字,所以一定包含了重复的数字。我们把1 ~ n的数字从中间数字m分成两部分,前一半是1 ~ m,后一半是m+1 ~ n,如果1 ~ m的数字数目超过了m,那么前一半一定有重复的数字;否则,后一半一定包含了重复的数字,我们继续把有重复数字的区间一分为二,直到找到一个重复的数字。

bool duplicate(int numbers[], int length, int* duplication) {
        if(numbers==nullptr || length<=0){
            return false;
        }
        int start=1;
        int end=length-1;
        while(end>=start){
            int mid=(start+end)/2;
            int count=countRange(numbers, length, start, mid);
            if(end==start){
                if(count>1){
                    *duplication=start;
                    return true;
                }
                else
                    break;
            }
            if(count>(mid-start+1))
                end=mid;
            else
                start=mid+1;
        }
        return false;
    }
    
    int countRange(const int* numbers, int length, int start, int end){
        if(numbers==nullptr || length<=0){
            return 0;
        }
        int count=0;
        for(int i=0;i<=length;i++){
            if(numbers[i]>=start && numbers[i]<=end){
                count++;
            }
        }
        return count;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值