剑指offer第3题——找出数组中重复的数字

面试题3——找出数组中重复的数字(反证法)

在一个长度为n的数组里所有整数都在m~m+n-1范围内,(注意原题是长度为n的所有数字都在0~n-1的范围内,但这里我强调了范围的偏移m,随着解释会慢慢清楚的)。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组[3,5,-1,-2,0,3,5],那么对应的输出是数字3或5.

解析:笨方法是来一个i、i+1的遍历,使数组中所有整数都两两比较,能找出所有重复数字,这时间复杂度是O(n2),显然是很大的。或者来一个排序,然后走一趟遍历,也能找出所有重复数字,还能比较直接地统计出每个重复数字的重复次数,时间复杂度是O(nlogn)+O(n)。

然,还有一种神奇的算法能实现时间复杂度O(n)并能找出所有重复数字:注意到n维数组所有元素的区间都落在m~m+n-1范围内,注意从m到m+n-1之间满打满算也只有n个整数,则假设数组没有重复的元素(反证思想),则排序后的数组就是从小到大的[m,m+1,m+2,...,m+n-2,m+n-1],是紧密相连的,每个元素在有序数组中都有自己独有的位置,并且有规律:整数x一定在有序数组的x-m下标处,因为是密布整数

所以,如果数组中有重复整数y,那在第二个y要填到有序数组中时就会发现其下标y-m处已经被第一个y给占了,此时就是检测到重复整数y了。

算法:

#include <iostream>

int RepetNum(int arr[],size_t len,int *&arrRepet){

    /*

    首先统计数组的区间[m,n],若是n-m+1==len,也就是数组最小值m到最大值n之间如果密布整数的话

    则一共会有n-m+1个数,正好是待考察数组的元素总数,满足这个巧合才能用"假设数组密布则元素k在

    有序数组中下标为k-m"来判断考察数组是否密布(若不密布则一定有重复元素,重复元素必定计算出

    同一下标)

    */

    int nmin(arr[0]),nmax(arr[0]);

    for(size_t i=1;i<len;i++){

        nmin=arr[i]<nmin?arr[i]:nmin;

        nmax=arr[i]>nmax?arr[i]:nmax;

    }

    if( !((nmax-nmin+1)==len) ) {

        arrRepet=nullptr;

        return -1;//不适用于本法

    }

    else{

        //可假设区间满布来检查重复元素

        int arr2[len];  //有序数组

        for(size_t i=0;i<len;i++) arr2[i]=nmin-1; //有序数组初始化为[nmin,nmax]之外的值,防止误判

        arrRepet = new int [len/2]; //最多有len/2个重复的元素

        size_t nCount(0); //重复元素组数

        for(size_t i=0;i<len;i++){

            if(arr[i]==arr2[arr[i]-nmin]){

                //arr[i]被期望在有序数组arr2的下标arr[i]-nmin处,如果有序数组

                //arr2[arr[i]-nmin]已经有值arr[i]了,那说明此时的arr[i]是重复的

                arrRepet[nCount++]=arr[i];

            }else{ //arr[i]是同值第一个出现的,放到有序数组arr2中

                arr2[arr[i]-nmin]=arr[i];

            }

        }

        return nCount;

    }

}

 

int main(int argc,char *argv[]){

    int arr[]={9,7,8,7,8,4};

    int nRepet(0);

    int *arrRepet(nullptr);

    nRepet=RepetNum(arr,6,arrRepet);

 

}

猜你喜欢

转载自blog.csdn.net/HayPinF/article/details/108350910
今日推荐