目录
牛客高赞(和思路二类似都是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;
}