剑指offer|键指offer——面试题3(数组中重复的数字(p37-43))

面试题3:数组中重复的数字 题目一:找出数组中重复的数字
题目:
在一个长度为n的数组里的所有数字都在0~n-1范围内。数组中某些数字是重复的,请找出数组中任意一个重复的数字。例如:输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复数字2或者3。
注意仔细读题,尤其是这些信息: 长度为n;数字范围是0~n-1(因此也一定是有重复数字的);找出任意一个。
动手写代码之前,问清楚面试官的需求,是求任意一个重复字符还是求出全部的重复字符。
分析:

  1. 可以用哈希表:从头到尾扫描数字,每扫描到一个数字就判断哈希表里是否已经包含了该数字,没有则加入哈希表,若有则表明哈希表里已经存在了该数字,就找到了这个重复的数字了。
  2. 数组重排法(本代码选择该方法):从头到尾扫描,比较该数字x是不是等于下标i,若是则接着再扫描下一个数字,若不是则再拿它和第i=x个数字作比较,若相等则找到了一个重复的数字;若不相等,则把第i个数字和第x个数字做交换,把x放到属于它的位置上,重复这个过程只到找出了重复字符。
    这么说起来是有点乱啦,我们来画图举例吧:
    剑指offer|键指offer——面试题3(数组中重复的数字(p37-43))
    文章图片

    就相当于给数组排了个序,一旦找到后边数字有和前边已经排好顺序的数字有重复的,就结束了。
伪代码:
int main() { 定义数组a[],数组长度n,当前数值x,结果值r; 输入n; 循环输入a[]; //特殊处理 输入空指针或者长度<=0的情况; 元素值超范围:小于0或者大于n-1的情况; //操作(要不要定义成一个函数来被调用?) for(int i=0; i

代码: DuplicationInArray.cpp
#include using namespace std; int main() { int *num,n,x,r[1]; scanf("%d", &n); num = (int*)malloc(sizeof(int)*n); for(int i=0; in-1) return 1; } //操作 int Temp, j=0, flag=1; for(int i=0; i

为什么这么写呢?
因为题目只要求找到任意一个重复的代码就行了,没有说找到全部的重复代码,所以只输出一个重复的值即可。
这次我命名了DuplicationInArray.cpp这样方便之后复习,也显得更专业点。
源码也有:
//源代码 #include //定义一个布尔函数:有重复值返回true,否则返回false //子函在前,主函里就不用声明了 bool duplicate(int numbers[], int length, int* duplication) { //无效值特殊处理 if(numbers == nullptr || 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) { //这里对于num[i]与i的值用了while判断 while(numbers[i] != i) { if(numbers[i] == numbers[numbers[i]]) { *duplication = numbers[i]; return true; //有重复值则返回true }// 交换numbers[i]和numbers[numbers[i]] int temp = numbers[i]; numbers[i] = numbers[temp]; numbers[temp] = temp; } }return false; }// ====================测试代码==================== //这是啥?答:为了验证这个重复值是否是预期array里重复了的值 bool contains(int array[], int length, int number) { for(int i = 0; i < length; ++i) { if(array[i] == number) return true; }return false; } //测试函数 //testName表测试几 //expected[]表期望结果值,也就是都有哪个值给重复了 //expectedExpected表期望结果值的sizeof //validArgument表示有没有重复的函数 void test(char* testName, int numbers[], int lengthNumbers, int expected[], int expectedExpected, bool validArgument) { //输出一串字:表示测试几开始了 printf("%s begins: ", testName); //调用这个布尔函数 int duplication; bool validInput = duplicate(numbers, lengthNumbers, &duplication); if(validArgument == validInput)//看输入是否有效 { if(validArgument) { if(contains(expected, expectedExpected, duplication))//duplication是之前duplicate(numbers, lengthNumbers, &duplication); 里的 printf("Passed.\n"); else printf("FAILED.\n"); } else printf("Passed.\n"); } else printf("FAILED.\n"); }// 重复的数字是数组中最小的数字 void test1() { int numbers[] = { 2, 1, 3, 1, 4 }; //发现了吗?空括号可以这么去定义 int duplications[] = { 1 }; test("Test1", numbers, sizeof(numbers) / sizeof(int), duplications, sizeof(duplications) / sizeof(int), true); }// 重复的数字是数组中最大的数字 void test2() { int numbers[] = { 2, 4, 3, 1, 4 }; int duplications[] = { 4 }; test("Test2", numbers, sizeof(numbers) / sizeof(int), duplications, sizeof(duplications) / sizeof(int), true); }// 数组中存在多个重复的数字 void test3() { int numbers[] = { 2, 4, 2, 1, 4 }; int duplications[] = { 2, 4 }; test("Test3", numbers, sizeof(numbers) / sizeof(int), duplications, sizeof(duplications) / sizeof(int), true); }// 没有重复的数字 void test4() { int numbers[] = { 2, 1, 3, 0, 4 }; int duplications[] = { -1 }; // not in use in the test function test("Test4", numbers, sizeof(numbers) / sizeof(int), duplications, sizeof(duplications) / sizeof(int), false); }// 没有重复的数字 void test5() { int numbers[] = { 2, 1, 3, 5, 4 }; int duplications[] = { -1 }; // not in use in the test function test("Test5", numbers, sizeof(numbers) / sizeof(int), duplications, sizeof(duplications) / sizeof(int), false); }// 无效的输入 void test6() { int* numbers = nullptr; int duplications[] = { -1 }; // not in use in the test function test("Test6", numbers, 0, duplications, sizeof(duplications) / sizeof(int), false); }void main() { test1(); test2(); test3(); test4(); test5(); test6(); }

题目二:不修改数组找出重复的数字
题目:
在一个长度为n+1的数组里所有的数字都在1~n范围内,所以至少有一个数是重复的,在不修改输入数组的情况下找出任意一个重复的数字。
分析:
  1. 思路1:新建一个数组array来复制输入的数组,对应的数放在该对应下标位置处,要是有重复的值立马就能找到。
  2. 思路2:利用二分查找法解决,这也是书中的方法。例如长为8的数组{2 3 5 4 3 2 6 7},数字范围在1-7内,中间数字4将其分为1-4和5-7。统计1-4在整个数组中出现的次数,若大于4,则必定有重复数。将1-4继续分成1-2和3-4,统计1-2在整个数组中的次数为2;再统计3-4在整个数组中出现的次数为3,所以3和4里必定有一个数是重复数,这样就找到了。
思路1伪代码:
定义数组*num,长度n+1,新数组*Array; 输入长度n+1; 遍历输入数组num; 遍历输入数组Array[]并初始化为0; //特殊处理 输入无效的情况; 数组里的值超范围的情况; //处理 遍历num[]: 比较array[i]的值与num[i]的值,若不一样: 将num[i]放到Array[num[i]]处; 否则: 这个num[i]的值就是那个重复的值,赋值给结果r; break; 输出r; return 0;

思路1代码:
记得找到重复值就break跳出循环!由于这个问题导致我半天没解出来。
#include using namespace std; int main() { int *num,*Array; int n, r=0; scanf("%d", &n); num = (int*)malloc(sizeof(int)*n); Array = (int*)malloc(sizeof(int)*n); for(int i=0; in-1) return 1; } //特殊处理 if(num==nullptr || n<=0) return 1; //操作 for(int i=0; i

思路2伪代码:
定义数组*num,长度n+1; 输入长度n+1; 遍历输入数组num; //特殊处理 输入无效的情况; 数组里的值超范围的情况; //处理 int start = 1; int end =n-1; while(end>start) { 定义中间值middle=((end-start)>>1)+start; 定义计数值count; count = 中分后的这几个数在整个数组中出现次数; 若为一个数(输入可能全部为同样的值)时:若出现次数count>1,则该值就是重复值r=start; 若不为一个数时: 若出现次数count>数值范围: 遍历二分后的前半部分值:end=middle; 否则: 遍历二分后的后半部分值start=middle+1; } 输出r; return 0;

【剑指offer|键指offer——面试题3(数组中重复的数字(p37-43))】思路2代码:
(end-start)>>1右移相当于(end-start)/2,左进位。
#include using namespace std; int main() { int *num; int n, r=0; scanf("%d", &n); num = (int*)malloc(sizeof(int)*n); for(int i=0; in-1) return 1; } //特殊处理 if(num==nullptr || n<=0) return 1; //处理 int start = 1; int end =n-1; //迭代循环 while(end>=start) { middle = ((end-start)>>1)+start; int count=0; //统计这几个数在整个数组中出现的次数 for(int i=0; i=start && num[i]<=end) { count++; } } //一个数时:start == end就表明这是一个数 if(end == start) { if(count>1) { r=start; break; } else { break; } //不为一个数时:end>start else { if(count>(middle-start+1)) { end = middle; } else { start = middle+1; } } } cout<

    推荐阅读