剑指offer 3 数组中重复的数字 以及变形题(不修改数组的情况下找出重复的数字)

题目:

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

例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3。

思路:

1、排序

然后从头到尾扫描数组

时间复杂度:O(nlogn)  

2、利用哈希表

从头到尾按顺序扫描数组中的每个数字,每扫描到一个数字的时候,都可以用O(1)的时间判断哈希表里是否已经包含了该数字。如果哈希表里还没有这个数字,就把他加入哈希表,如果哈希表里已经存在该数字,就找到一个重复的数字。

时间复杂度:O(n)   空间复杂度:O(n)

3、对比下标法

由于题目要求长度为n的数组里的所有数字都在0到n-1的范围内,如果不重复,则排完序后,下标应该与其值相等。按照这个规则,找到含有重复数字数组里的重复数字。

以数组{2,3,1,0,2,5,3}为例

下标为0的数字是2,与它的下标0不相等,那么我们把它和下标为2的数字比较,如果不相等则交换,数组变为{1,3,2,0,2,5,3}

再看下标为0的数字是1,与它的下标0不相等,同样的,把它与下标为1的数字比较,如果不相等则交换,数组变为{3,1,2,0,2,5,3}

再看下标为0的数字是3,与它的下标0不相等,同样的,把它与下标为3的数字比较,如果不相等则交换,数组变为{0,1,2,3,2,5,3}

再看下标为1,2,3的数字,下标均与其值相等

下标为4的数字是2,与其下标不等,比较它和下标为2的数字,相等,那么找到一个重复数字。

时间复杂度:O(n) (因为while循环中的每个位置最多需要两次就能找到对的值), 空间复杂度O(1)

具体代码为:

class Solution {
public:
    // Parameters:
    //        numbers:     an array of integers
    //        length:      the length of array numbers
    //        duplication: (Output) the duplicated number in the array number
    // Return value:       true if the input is valid, and there are some duplications in the array number
    //                     otherwise false
    bool duplicate(int numbers[], int length, int* duplication) {
        if(length<=0)
        return false;
        for(int i=0;i<length;i++)
        {
          if(numbers[i]<0||numbers[i]>length-1)
              return false;
        }//以上是确保输入数组符合题的要求
        for(int i=0;i<length;i++)
        {
           while(i!=numbers[i])//注意需要用while循环
            {
                if(numbers[i]!=numbers[numbers[i]])
                    swap(numbers,i,numbers[i]);
                else 
                {  *duplication=numbers[i];
                      return true;}
            }
        }
        return false;       
    }
    void swap(int arr[],int i,int j)
    {
        int temp;
        temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }
};

变形题:在不修改数组的情况下找出重复的数字

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

思路:

可以使用上题的算法:现在是如果没有重复的数字,那么按照顺序排列后,数组值和下标相差1。

但题目要求不能修改原始数组,所以不能使用。

思路2:

可以创建一个长度为n+1的辅助数组,如何逐一把原数组的每个数字复制到辅助数组。如果原数组中被复制的数字是m,则把它复制到辅助数组中下标为m的位置。这样就很容易发现哪个数字是重复的。

但这需要O(n)的辅助空间

思路3:

二分查找的思路

以长度为8的数组{2,3,5,4,3,2,6,7}为例分析查找的过程。

1、根据题目要求,这个长度为8的所有数字都在1~7的范围内。中间的数字4把1~7分为两段,一段是1~4,另一段是5~7。

2、分析1~4中的数字在数组中出现的次数,一共出现了5次,因此这四个数中一定存在重复数字。

3、把1~4的范围一分为2,一段时1~2,另一段是3~4

4、分析1~2中数字出现的次数,两次故没有重复数字。再分析3~4中数字的重复次数,三次,那么3或4肯定有一个是重复数字

5、将3~4分为两段,一段是3,一段是4,分析3出现的次数是2,所以3是一个重复的数字。

时间复杂度:如果输入长度为n的数组,函数countRange将被调用O(logn)次,每次都需要O(n)的时间,因此总的时间复杂度是O(nlogn)

空间复杂度:O(1)

代码:

int getDuplication(const int* numbers,int length)

{
if(numbers==NULL||length<=0)//判断输入的数是否符合要求

return -1;

int start=1;//因为长度为8的所有数字都在1~7的范围内,所以start=1,end=length-1
int end=length-1;

while(start<=end)

{
int middle=start+(end-start)/2);

int count=countRange(numbers,length,start,middle);

if(end==start)  

{
 if(count>1)

return start;

else break;

}

if(count>(middle-start+1))//如果1~4中的数字在数组中出现了5次,因此这四个数中一定存在重复数字。
end=middle;

else start=middle+1;

}

return -1;

}

/* 计算某一区间中的数在数组中出现的次数*/

int countRange(const int*numbers,int length,int start,int end)

{
if(numbers=NULL)

return 0;

int count=0;

for(int i=0;i<length;i++)

if(numbers[i]>=start&&numbers[i]<=end)

++count;

return count;

}

缺点:这个算法找不出像数组{2,3,5,4,3,2,6,7}这样的重复数字。想一想为什么呢?

因为在1-2区间内,count=2,被判断为这个范围内没有重复的数字。

猜你喜欢

转载自blog.csdn.net/weixin_41413441/article/details/80587096