需求
不修改数组找出重复的数字。
在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,单不能修改输入的数组。例如,如果输入长度为8的数组{2,3,5,4,6,2,6,7},那么对应的输出是重复的数字2或者6。(不要求找出所有重复数字,只要找出其中之一即可)
分析
由于不能修改输入的数组,因此不能在原始数组中做排序再判断下标。需要考虑其他的方法。
思路一:
使用一个辅助的数组,长度为n,如:tmp[n],然后开始遍历原始数组
1.如果数组中出现数字x,就判断数组下标为x的节点是否还是初始值0,即tmp[x]是否为0;
2.如果tmp[x] == 0,就标记为1,然后继续遍原始数组的下一个元素,继续步骤1;
3.如果tmp[x] != 0,说明之前已经表标记为1了,即之前已经出现过这个相同的元素,即已经找到重复数字了。
见示例代码中的findRepeatNumberWithExArray。
思路二:
暂且称之为分段法。
假如数组如下{2,3,5,4,6,2,6,7}
数组的取值范围在1~7,数组的长度为8,所以在1~7里面肯定是有1个或者1个以上重复的数字。
1.把1~7的取值范围分成两部分,前半部分为1~4,后半部分是5~7;
2.计算1~4的数字有多少个,从此例子来看,一共有4个(2,3,4,2),1~4之间范围为4,数组的长度为4,有可能有重复的数字也有可能没有重复的数字;
3.由于剩下的数字范围是5~7范围为3,数组长度是4(总长度8减去前半部分数字的长度,就是后半部分剩余的长度4),因此肯定有重复数字;
4.查找范围变成5~7,重复步骤1,把取值范围分成两部分,前半部分是5~6,后半部分是7;接着继续步骤二;
...
最后.当查找的数字范围变成1,并且计算得出的数字个数大于1,说明这个数字就是重复的数字,任务结束。
见实例代码中的findRepeatNumberBySegment。
c++示例代码如下:
1 #include <iostream> 2 3 using namespace std; 4 5 const int ARR_LENGTH = 8; 6 7 /************************************************************************/ 8 /* @brief 使用额外的辅助数组查找数组中其中一个重复数字 9 /* @param arr数组 10 /* @param length数组长度 11 /* @return 数组中其中一个重复的数字,-1表示没找到 12 /************************************************************************/ 13 int findRepeatNumberWithExArray(const int* arr, const int length) 14 { 15 int result = -1; 16 17 //入参有问题直接返回,少于2个数字肯定没有重复的 18 if (!arr || length < 2) 19 { 20 return result; 21 } 22 23 //一个临时的辅助数组 24 int *tmpArr = new int[length]; 25 memset(tmpArr, 0, sizeof(int)*length); 26 27 for (int i = 0; i < length; ++i) 28 { 29 if (tmpArr[arr[i]] != 0) 30 { 31 result = arr[i]; 32 break; 33 } 34 else 35 { 36 tmpArr[arr[i]] = 1; 37 } 38 } 39 delete[] tmpArr; 40 tmpArr = nullptr; 41 return result; 42 } 43 44 /************************************************************************/ 45 /* @brief 计算数组中在某一个范围内的数字的数量 46 /* @param arr数组 47 /* @param length数组长度 48 /* @param start范围开始位置(包括start) 49 /* @param end范围结束位置(包括end) 50 /* @return 在范围内的数字的数量 51 /************************************************************************/ 52 int countRange(const int* arr, const int length, const int start, const int end) 53 { 54 int counter = 0; 55 if (!arr || length < 1 || end < start) 56 { 57 return counter; 58 } 59 60 for (int i = 0; i < length; ++i) 61 { 62 //如果数字在范围内则把个数加1 63 if (arr[i] >= start && arr[i] <= end) 64 { 65 counter++; 66 } 67 } 68 return counter; 69 } 70 71 /************************************************************************/ 72 /* @brief 使用分段法查找数组中其中一个重复数字 73 /* @param arr数组 74 /* @param length数组长度 75 /* @return 数组中其中一个重复的数字,-1表示没找到 76 /************************************************************************/ 77 int findRepeatNumberBySegment(const int* arr, const int length) 78 { 79 int result = -1; 80 81 //入参有问题直接返回,少于2个数字肯定没有重复的 82 if (!arr || length < 2) 83 { 84 return result; 85 } 86 87 int start = 1; 88 int end = length - 1; 89 int counter = 0; 90 91 while (start <= end) 92 { 93 int middle = (end - start) / 2 + start; 94 //计算前半部分的数字的数量 95 counter = countRange(arr, length, start, middle); 96 //只有一个数字并且数量大于1,说明找到了重复的数字了 97 if (start == middle && counter > 1) 98 { 99 result = start; 100 break; 101 } 102 103 //假如前半部分数字数量少于等于范围,说明这个范围可能有重复的也有可能没有重复的数字,但是后一半的数字肯定有重复的,直接在后一半查找 104 if (counter <= (middle - start + 1)) 105 { 106 start = middle + 1; 107 } 108 //假如前半部分数字的数量大于数字范围,说明这个范围内肯定有重复的数字,继续查找 109 else 110 { 111 end = middle; 112 } 113 } 114 return result; 115 } 116 117 int main() 118 { 119 int arr[ARR_LENGTH] = {2,3,5,4,6,2,6,7}; 120 121 cout << "原始数据:" << endl; 122 123 for (int i = 0; i < sizeof(arr) / sizeof(int); ++i) 124 { 125 cout << arr[i] << " "; 126 } 127 128 int repeat = findRepeatNumberWithExArray(arr, ARR_LENGTH); 129 cout << " 辅助数组法查找重复数字:" << repeat << endl; 130 131 repeat = findRepeatNumberBySegment(arr, ARR_LENGTH); 132 cout << " 分段法查找重复数字:" << repeat << endl; 133 134 return 0; 135 }