剑指offer面试题三 :数组中重复的数字( google 面试)

版权声明:敲一敲看一看 https://blog.csdn.net/idealhunting/article/details/84944885

目录

参考博客:

题目一:找出数组中重复的数字

思路一

思路二

题目二:不修改数组找出重复的数字

测试:

牛客:

 牛客高赞(和思路二类似都是hash映射,网友思路真是脑洞大开,这里相关溢出问题考虑的只有~(1<<31)>>1,其他的还真没挑出刺,本身就能ac)


参考博客:

1.在N个乱序数字中查找第k大的数字

https://blog.csdn.net/u010412301/article/details/67704530

2[经典面试题][谷歌]一个大小为n的数组,里面的数都属于范围[0, n-1],有不确定的重复元素,找到至少一个重复元素https://blog.csdn.net/sunnyyoona/article/details/43883519

3.【排序算法】基数排序:LSD 与 MSD

https://blog.csdn.net/xgf415/article/details/76595887

https://baike.baidu.com/item/%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F/7875498?fr=aladdin

基数排序

基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。

时间效率 :设待排序列为n个记录,d个关键码,关键码的取值范围为radix,则进行链式基数排序的时间复杂度为O(d(n+radix)),其中,一趟分配时间复杂度为O(n),一趟收集时间复杂度为O(radix),共进行d趟分配和收集。 空间效率:需要2*radix个指向队列的辅助空间,以及用于静态链表的n个指针

题目一:找出数组中重复的数字

   在一个长度为n的数字里所有数字范围都在0~n-1范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,在一个长度{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3。

思路一

寻找重复元素,很容易想到建立哈希表来完成,遍历一遍数组就可以将每个元素映射到哈希表中。如果哈希表中已经存在这个元素则说明这就是个重复元素。这种方法可以很方便的在O(n)时间内完成对重复元素的查找。可是题目要求在O(1)的空间。因此采用哈希表这种解法肯定在空间复杂度上是不符合要求的。题目中数组中所以数字都在[0, n-1]区间范围内,因此哈希表的大小为n。因此我们实际要做的就是对n个范围为0到n-1的数进行哈希,而哈希表的大小刚好为n。对排序算法比较熟悉的同学不难发现这与一种经典的排序算法(基数排序)非常类似。而基数排序的时间空间复杂度刚好符合题目要求。因此尝试使用基数排序来解这道面试题。

int dupicate1(int a[],int n){
    if(a==nullptr||n<=0)return -1;
    for(int i=0;i<n;++i){
        if(a[i]<0||a[i]>n-1)return -1;
    }
    for(int i=0;i<n;++i){
        while(a[i]!=i){
            if(a[i]==a[a[i]]){
                return a[i];
             }
             swap(a[i],a[a[i]]);
        }
    }
}

思路二

第一次遍历:对于每一个A[i] = A[i] * n 
第二次遍历:对于每一个i,A[A[i]/n]++ 
第三次遍历:对于每一个i,A[i] % n就是出现次数 
A[i]应该出现在A中的A[i]位置,乘以n、再除以n,很容易的来回变换;第二次遍历,对于A[i]本来所在的位置不断增1,但绝对不对超出n的,那每一个i出现的次数,就是A[i]对n取余。

vector<int> duplicate2(int a[],int n){//每个元素出现次数情况
    vector<int> ans;
    if(n <= 0)return ans;
    for(int i = 0;i < n;++i){
        a[i] *= n;
    }
    for(int i = 0;i < n;++i){
        ++a[a[i]/n];
    }
    int count;
    for(int i = 0;i < n;++i){
         count= a[i] % n;
        if(count > 1){
            ans.push_back(i);
       }
    }
    return ans;
}

题目二:不修改数组找出重复的数字

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

思路:

即为了求数字(1~n)的每个数字在一个大小为n+1的数组里出现的次数,这里所求的是只要有一个数字出现一次以上及为所求数字,那么可以简单的取1~n的中位数m将n个数字分为两部分,遍历数组,求解1~m在数组中出现的次数t1和m+1~n在数组中出现的次数t2,如果t1>m(m为前部分的个数)(那么这1~m中必然存在一个数在数组中重复出现,这时继续将前m个数继续划分为两部分求解,直到剩下一个元素,那么这元素必定是重复的那个)否则m+1~n中必然存在一个数在数组中重复出现,这时候将后面n-(m+1)+1个数划分为两部分求解,直到剩下一个元素。(这里不能保证找到所有,因为是靠范围缩小,查找。例如在1~2范围查找时,2出现了两处,这里判定了1~2的1,2和而没有重复出现)

int getDuplication(const int* numbers,int length){
    if(numbers==nullptr||length<=0)return -1;
    int start=1;
    int end=length-1;
    while(end>=start){
        int middle=((end-start)>>1)+start;
        int count=countRange(numbers,length,start,middle);
        if(end==start){//如果仅剩一个元素且这个元素在数组中出现的次数大于1,返回start即这个数字
            if(count>1)return start;
            else break;
        }
        if(count>(middle-start+1))//前部分中每个数字在数组出现的总次数大于这个部分的个数,这这部分数字存在重复出现的
            end=middle;    //更新查找范围为 start middle
        else start=middle+1;//否则更新范围为middle+1 end
    }
    return -1;
}
int countRange(const int* numbers,int length,int start,int end){//判断数字(stat~end)在数组中出现的次数
    if(numbers==nullptr)
        return 0;
    int count=0;
    for(int i=0;i<length;i++)
        if(numbers[i]>=start&&numbers[i]<=end)
        ++count;
    return count;
}

测试:

#include <iostream>
#include <algorithm>
#include<vector>
using namespace std;
int dupicate1(int a[],int n){
    if(a==nullptr||n<=0)return -1;
    for(int i=0;i<n;++i){
        if(a[i]<0||a[i]>n-1)return -1;
    }
    for(int i=0;i<n;++i){
        while(a[i]!=i){
            if(a[i]==a[a[i]]){
                return a[i];
             }
             swap(a[i],a[a[i]]);
        }
    }
}
//void  duplicate2(int a[],int n)//直接用原数组保存,输出每个数字出现的次数
vector<int> duplicate2(int a[],int n){//每个元素出现次数情况
    vector<int> ans;
    if(n <= 0)return ans;
    for(int i = 0;i < n;++i){
        a[i] *= n;
    }
    for(int i = 0;i < n;++i){
        ++a[a[i]/n];
    }
    int count;
    for(int i = 0;i < n;++i){
         count= a[i] % n;
        if(count > 1){
            ans.push_back(i);
       }
    }
    return ans;
}
int countRange(const int* numbers,int length,int start,int end){
    if(numbers==nullptr)
        return 0;
    int count=0;
    for(int i=0;i<length;i++)
        if(numbers[i]>=start&&numbers[i]<=end)
        ++count;
    return count;
}

int getDuplication(const int* numbers,int length){
    if(numbers==nullptr||length<=0)return -1;
    int start=1;
    int end=length-1;
    while(end>=start){
        int middle=((end-start)>>1)+start;
        int count=countRange(numbers,length,start,middle);
        if(end==start){
            if(count>1)return start;
            else break;
        }
        if(count>(middle-start+1))
            end=middle;
        else start=middle+1;
    }
    return -1;
}

int my_countRange(int a[],int length,int start,int end){
    if(a==nullptr)return 0;
    int count=0;
    for(int i=0;i<length;i++)
        if(a[i]>=start&&a[i]<=end)++count;
    return count;
}
//{2, 3, 5, 4, 3, 2, 6, 7};
int my_getDuplication(int a[],int length){
    if(a==nullptr||length<=0)return -1;
    int start=1;
    int end=length-1;
    while(start<=end){
        int middle=((end-start)>>1)+start;
        int count=my_countRange(a,length,start,middle);
        if(end==start){
            if(count>1)return start;
            else break;
        }
        if(count>(middle-start+1))
            end=middle;
        else start=middle+1;
    }
    return -1;
}



int main(){
    int numbers1[]{2,3,1,0,2,5,3};
//********************* 剑指offer 面试题三 题目 一 Test 找出数组中重复的数字 **************************
             /* 题目一    在一个长度为n的数字里所有数字范围都在0~n-1范围内。
             **数组中某些数字是重复的,但不知道有几个数字重复了,
             **也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
             **例如,在一个长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3。*/
//**************************** 思路一 *****************************************************************
    int ans1=dupicate1(numbers1,7);
    cout<<"Test1: duplicate1_ans(思路一 基数排序求的重复的一个数字): "<<ans1<<endl;

//**************************** 思路二 *****************************************************************
    vector<int> ans2=duplicate2(numbers1,7);
    cout<<"Test2: duplicate2_ans(思路二 多次hash映射 求得每个数字出现的次数/重复出现的数字): "<<endl;
    for(auto it:ans2){
        cout<<it<<endl;
    }
             /*题目二
             **在一个长度为n+1的数组里的所有数字都在1到n的范围内,所以数组中至少有一个数字是重复的。
             ** 请找出数组中任意一个重复的数字,但不能修改输入的数组。
             **例如,如果输入长度为8的数组{2, 3, 5, 4, 3, 2, 6, 7},那么对应的输出是重复的数字2或3.
             */

//********************* 剑指offer 面试 题三 题目二 Test 不修改数组找出重复的数字 **********************
    int numbers2[]{2, 3, 5, 4, 3, 2, 6, 7};
    cout<<"Test3:my_getDuplication(题目二 二分求解重复的数字):"<<endl;
    int ans3=my_getDuplication(numbers2,8);
    cout<<ans3;

}

牛客:

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(numbers==nullptr||length<2)return false;
        bool flag=true;
        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(numbers[i]!=i){
                if(numbers[i]==numbers[numbers[i]]){
                    *duplication=numbers[i];
                    return true;
                }
                swap(numbers[i],numbers[numbers[i]]);
            }
        } 
        return false; 
    }
};

 牛客高赞(和思路二类似都是hash映射,网友思路真是脑洞大开,这里相关溢出问题考虑的只有~(1<<31)>>1,其他的还真没挑出刺,本身就能ac)

链接:https://www.nowcoder.com/questionTerminal/623a5ac0ea5b4e5f95552655361ae0a8
来源:牛客网

不需要额外的数组或者hash table来保存,题目里写了数组里数字的范围保证在0 ~ n-1 之间,
所以可以利用现有数组设置标志,当一个数字被访问过后,可以设置对应位上的数 + n,
之后再遇到相同的数时,会发现对应位上的数已经大于等于n了,那么直接返回这个数即可。

代码是C:

int find_dup( int numbers[], int length) {

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

        int index = numbers[i];

        if (index >= length) {

            index -= length;

        }   

        if (numbers[index] >= length) {

            return index;

        }   

        numbers[index] = numbers[index] + length;

    }   

    return - 1 ; 

}
作者:zhili.jzl
链接:https://www.nowcoder.com/questionTerminal/623a5ac0ea5b4e5f95552655361ae0a8
来源:牛客网

 bool duplicate(int numbers[], int length, int* duplication) {
        for (int i = 0; i < length; i++) {
            int index = numbers[i] % length;
            if (numbers[index] < length) {
                numbers[index] += length;
            } else {
                *duplication = index;
                return true;
            }
        }
        return false;
    }

猜你喜欢

转载自blog.csdn.net/idealhunting/article/details/84944885