本文參考自《劍指offer》一書,代碼采用Java語言。
題目
在一個長度為n+1的數組里的所有數字都在1到n的范圍內,所以數組中至少有一個數字是重復的。請找出數組中任意一個重復的數字,但不能修改輸入的數組。例如,如果輸入長度為8的數組{2, 3, 5, 4, 3, 2, 6, 7},那么對應的輸出是重復的數字2或者3。
思路
數組長度為n+1,而數字只從1到n,說明必定有重復數字。可以由二分查找法拓展:把1~n的數字從中間數字m分成兩部分,若前一半1~m的數字數目超過m個,說明重復數字在前一半區間,否則,在后半區間m+1~n。每次在區間中都一分為二,知道找到重復數字。
更簡單的思路:把該數組看作一個鏈表,下標代表當前結點,值代表next指針,具體參考Find the Duplicate Number,時間復雜度僅為O(n)
測試用例
1.數組中帶一個或多個重復數字
2.數組中不包含重復的數字
3.無效輸入測試用例(空數組,數組數字越界等)
完整Java代碼
(含測試代碼)
/**
*
* @Description 不修改數組找出重復的數字
*
* @author yongh
* @date 2018年7月16日 上午11:47:44
*/
/*
* 題目:在一個長度為n+1的數組里的所有數字都在1到n的范圍內,所以數組中至
* 少有一個數字是重復的。請找出數組中任意一個重復的數字,但不能修改輸入的
* 數組。例如,如果輸入長度為8的數組{2, 3, 5, 4, 3, 2, 6, 7},那么對應的
* 輸出是重復的數字2或者3。
*/
public class FindDuplication2 {
/**
* 找到數組中一個重復的數字
* 返回-1代表無重復數字或者輸入無效
*/
public int getDuplicate(int[] arr) {
if (arr == null || arr.length <= 0) {
System.out.println("數組輸入無效!");
return -1;
}
for (int a : arr) {
if (a < 1 || a > arr.length - 1) {
System.out.println("數字大小超出范圍!");
return -1;
}
}
int low = 1;
int high = arr.length - 1; // high即為題目的n
int mid, count;
while (low <= high) {
mid = ((high - low) >> 2) + low;
count = countRange(arr, low, mid);
if (low == high) {
if (count > 1)
return low;
else
break; // 必有重復,應該不會出現這種情況吧?
}
if (count > mid - low + 1) {
high = mid;
} else {
low = mid + 1;
}
}
return -1;
}
/**
* 返回在[low,high]范圍中數字的個數
*/
public int countRange(int[] arr, int low, int high) {
if (arr == null)
return 0;
int count = 0;
for (int a : arr) {
if (a >= low && a <= high)
count++;
}
return count;
}
// ==================================測試代碼==================================
/**
*數組為null
*/
public void test1() {
System.out.print("test1:");
int[] a = null;
int dup = getDuplicate(a);
if (dup >= 0)
System.out.println("重復數字為:" + dup);
}
/**
*數組數字越界
*/
public void test2() {
System.out.print("test2:");
int[] a = { 1, 2, 3, 4 };
int dup = getDuplicate(a);
if (dup >= 0)
System.out.println("重復數字為:" + dup);
}
/**
*數組帶重復數字
*/
public void test3() {
System.out.print("test3:");
int[] a = { 1, 2, 3, 2, 4 };
int dup = getDuplicate(a);
if (dup >= 0)
System.out.println("重復數字為:" + dup);
}
public static void main(String[] args) {
FindDuplication2 f2 = new FindDuplication2();
f2.test1();
f2.test2();
f2.test3();
}
}
test1:數組輸入無效!
test2:數字大小超出范圍!
test3:重復數字為:2
復雜度
時間復雜度說明:函數countRange()將被調用O(logn)次,每次需要O(n)的時間。
時間復雜度:O(nlogn) (while循環為O(logn),coutRange()函數為O(n))
空間復雜度:O(1)
