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

题目1:在一个长度为n的数组中所有数字的范围都在0~n-1的范围内,找出任意一个重复的数字,不存在的话返回-1。

解题思路1

先将数组排序,排序后重复的数字就会在相邻的位置,那么只要扫描一遍数组,对比相邻的数字就可以找出重复的数字。排序时间复杂度是O(nlogn),扫描数组复杂度是O(n),因此总的时间复杂度是O(nlogn),空间复杂度O(1)需要改变原数组

int duplicate(vector<int> numbers,int length)
{
    sort(numbers.begin(),numbers.end());
    for(int i=1;i<length;i++)
        if(numbers[i]==numbers[i-1])
            return numbers[i];
    return -1;
}

解题思路2

将数组排序的时间复杂度比较高,要更快地完成问题,我们需要想其他的方法。由于每个数字的值都在数组的下标的范围内,而且数组是随机存取的,赋值和访问的时间复杂度都是O(1),因此我们可以将值为k的数组放置在下标k的位置,通过扫描数组,每次都将一个数字放置到正确的下标位置,就能在最多n次操作后找到重复的数字(或者发现没有重复数字)。算法的做法是从下标0出发,(1)如果值与下标一致,则考量下一个下标;(2)如果值与下标不一致,如下标为index,值为k,如果k位置上的值已经是k,则k为重复值,否则交换index和k两个位置上的数,然后重复以上步骤。算法总的时间复杂度是O(n),空间复杂度O(1)需要改变原数组

void swap(int& a,int & b)
{
    int tmp = a;
    a = b;
    b = tmp;
}

int duplicate(vector<int> numbers,int length)
{
    for(int i=0;i<length;i++){
        while(numbers[i]!=i){
            if (numbers[numbers[i]]==numbers[i])
                return numbers[i];
            else
                swap(numbers[i],numbers[numbers[i]]);
        }
    }
    return -1;
}

解题思路3

如果不允许改变原数组,又需要用最快的速度完成任务,那么就可以考虑使用空间换时间。使用打表法,额外建立一个O(n)的数组,由于数组赋值和读取是O(1)时间的,因此对于每个数都可以O(1)时间内检查是否前面已经出现过,并在辅助数组中对应下标的位置做标志,总的时间复杂度是O(n),空间复杂度是O(n)不需要改变原数组

int duplicate(vector<int> numbers,int length)
{
    vector<bool> flag(length,false);
    for(int i=0;i<length;i++){
        if(flag[numbers[i]])
            return numbers[i];
        flag[numbers[i]] = true;
    }
    return -1;
 }

题目2:一个长度为n+1的数组中所有的数字范围都在1~n,找出其中任意一个重复的数字。

第二题是第一题加强了约束的版本,第一题可能存在没有重复数字的情况,而第二题根据鸽笼定理至少会有一个数字重复,因此除了上述的三种方法可以使用外,在不改变原数组,不使用额外空间的情况下还可以用二分的方法做。

解题思路4 二分法

将数字的范围二分成两个范围,总有一边数字数量会大于数字范围,根据鸽笼定理那边一定有重复数字。
二分的次数为O(logn)次,每次需要统计数字的时间复杂度是O(n),因此总的时间复杂度是O(nlogn),空间复杂度O(1)不需要改变原数组

int duplicate(vector<int> numbers,int length)
{
    int left =1, right = length-1;
    while(left<right){
        int mid = (left+right)/2;
        int count = 0;

        for(int i=0;i<length;i++)
            if (numbers[i]>=left && numbers[i]<=mid)
                count ++;

        if(count>mid-left+1)
            right = mid;
        else
            left = mid + 1;
    }
    return left;
}

猜你喜欢

转载自blog.csdn.net/acelove40/article/details/79776470