题目3:找出数组中任意重复的数字
在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2, 3, 1, 0, 2, 5, 3},那么对应的输出是重复的数字2或者3。
思路:
看到这道题后我的第一个想法是使用hash表对数组按顺序从头到尾进行扫描,扫描到不包含的数字就将其加入hash表中,扫描到hash表中包含的数字则代表该数字为重复的数字,则返回该重复的数字。这个算法的时间复杂度是O(n),但是它提高时间效率的代价是必须要有一个大小为O(n)的hash表。
在面试中,面试官除了会考察程序员是否能解决一个问题,还会关注他是否能考虑周全,比如使用尽量低的时间复杂度和空间复杂度的算法。因此,有时候我们仅仅只是做到能够解题是不够的,我们还需考虑有没有更好的办法。
第二个方法是对数组进行排序,然后对数组进行相邻两个数字的对比,找出相同的数字。排序一个长度为n的数组需要O(nlogn)的时间。这个方法的时间复杂度也比较高,再看看还有没有更优解。
重复查看题目,注意到所有数字都在0到n-1的范围,这样的话如果该数组不包含重复的数字,那么在排完序后数字i应出现在下角标为i的位置。
那么可以这样排序,对数组从头到尾扫描,扫描到n[i]时,判断n[i]是否等于i,是的话代表它在正确的位置,扫描下一个,不是的话先判断下角标为n[i]的数字是否等于i,是的话那么我们就找到这个重复的数字了,返回该数字退出循环即可,不是的话就将n[i]中的数字与下角标为n[i]的数字交换。一直循环直到扫描完整个数组。若循环完整个数组后仍然没有返回值,则代表该数组没有重复的数字。该方法虽然有个两重循环,但每个数字最多只需要交换两次就可以找到自己的位置,因此时间复杂度为O(n),至于空间复杂度,由于该方法全程都是在对原数组操作,故其空间复杂度为O(1)。故该方法为最优解。
注:在编写解决问题的函数之前先写单元测试用例,这是一种很好的习惯,在面试中会给面试官带来很好的印象。
关键代码实现:
private static int checkDuplicate(int[] ar) {
int duplicate;
if (ar == null) {// 避免数组为空的情况
return -2;
}
for (int i = 0; i < ar.length; i++) {// 避免出现数组内容格式错误
if (ar[i] < 0 || ar[i] > ar.length - 1) {
return -3;
}
}
for (int i = 0; i < ar.length; i++) {
while (ar[i] != i) {
if (ar[i] == ar[ar[i]]) {
duplicate = ar[i];
System.out.println(
"The array contains duplicate numbers."
+ "One of the duplicate number is: " + duplicate);
return duplicate;
}
swap(ar, i, ar[i]);
}
}
return -1;
}
//对数组中下角标为c和d的数字交换
private static void swap(int[] ar, int c, int d) {
int temp = ar[c];
ar[c] = ar[d];
ar[d] = temp;
}
下面对代码进行测试
测试用例及完整代码:
public class Duplication3 {
public static void main(String[] args) {
int[] a = { 3, 4, 1, 0, 2, 5, 6};// 数组中不包含重复数字
int[] b = { 3, 4, 1, 0, 2, 5, 3};// 数组中含有一个重复数字
int[] c = { 4, 6, 1, 0, 2, 5, 0};//数组中重复数字为最小值
int[] d = { 4, 6, 1, 0, 2, 6, 5};//数组中重复数字为最大值
int[] e = { 2, 3, 1, 0, 2, 5, 3 };// 数组中含有多个重复数字
int[] f = { 2, 3, 1, 0, -1, 5, 3};// 数组中的数字格式错误
int[] g = { 2, 3, 1, 0, 8, 5, 3};//数组中含有数字大于数组长度-1
int[] h = null;
test("test1",a);
test("test2", b);
test("test3", c);
test("test4", d);
test("test5", e);
test("test6", f);
test("test7", g);
test("test8", h);
}
private static void test(String s, int[] ar) {
System.out.println(s + ":");
int k = checkDuplicate(ar);
if (k < 0) {
switch (k) {
case -1:
System.out.println("No duplicate numbers in this array!");
break;
case -2:
System.out.println("This array is null!");
break;
case -3:
System.out.println("This array content is malformed!");
}
}
}
private static int checkDuplicate(int[] ar) {
int duplicate;
if (ar == null) {// 避免数组为空的情况
return -2;
}
for (int i = 0; i < ar.length; i++) {// 避免出现数组内容格式错误
if (ar[i] < 0 || ar[i] > ar.length - 1) {
return -3;
}
}
for (int i = 0; i < ar.length; i++) {
while (ar[i] != i) {
if (ar[i] == ar[ar[i]]) {
duplicate = ar[i];
System.out.println(
"The array contains duplicate numbers."
+ "One of the duplicate number is: " + duplicate);
return duplicate;
}
swap(ar, i, ar[i]);
}
}
return -1;
}
private static void swap(int[] ar, int c, int d) {
int temp = ar[c];
ar[c] = ar[d];
ar[d] = temp;
}
}