[剑指Offer] 3 数组中重复的数字
题目1:数组中重复的数字
描述
在一个长度为n的数组里的所有数字都在0~(n-1)的范围内。
数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。
请找出数组中任意一个重复的数字。
- 示例
输入:{2,3,1,0,2,5,3} 长度为7的数组
输出:2或者3 重复的数字
实现代码
class DuplicateNumber {
/**
* 暴力枚举
* 将数组中每个数与其他数对比 有重复就返回结果
* 最差结果 n^2 最后两个重复
*/
public static int bruteDuplicate(int[] arr, int length) {
if (arr.length <= 0 || length <= 0) {
return -1;
}
for (int i : arr) {
if (i < 0 || i > length - 1) {
return -1;
}
}
for (int i = 0; i < length; i++) {
for (int j = i+1; j < length; j++) {
if (arr[i] == arr[j]) {
return arr[i];
}
}
}
return -1;
}
/**
* 修改数组方法
* 逐个排序,在排序过程中寻找重复数字
* 从数组第一个数开始,将数字交换放入相应下标的位置,重复的数字必然会在交换过程中被发现
* 最差结果为 n-1 数组内数字全部交换完
*
* 有些类似桶方法,每个下标为一个桶,将元素放置到与下标相同的位置
* 限制:连续的元素
*/
public static int modifyArrayDuplicate(int[] arr, int length) {
if (arr.length <= 0 || length <= 0) {
return -1;
}
for (int i : arr) {
if (i < 0 || i > length - 1) {
return -1;
}
}
for (int i = 0; i < length; i++) {
// 将数组内数字和下标不相等的交换 直至发现数字重复
while (arr[i] != i) {
if (arr[i] == arr[arr[i]]) {
return arr[i];
}
// 交换
int temp = arr[i];
arr[i] = arr[temp];
arr[temp] = temp;
}
}
return -1;
}
}
- 测试代码
public class FindDuplicateNumber {
public static void main(String[] args) {
// 有多个重复数字
unitTest(new int[]{2,3,1,0,2,5,3});
// 有多个重复数字 偏后
unitTest(new int[]{1,2,3,4,5,5,1});
// 有一个重复数字 前后
unitTest(new int[]{0,2,3,4,5,6,0});
// 都是重复数字
unitTest(new int[]{2,2,3,3});
// 数组数字超出边界
unitTest(new int[]{2,3,7,0,2,5,3});
// 数组为空
unitTest(new int[]{});
}
private static void unitTest(int[] array) {
int length = array.length;
printArray(array);
System.out.println("暴力枚举" + DuplicateNumber.bruteDuplicate(array, length));
System.out.println("桶排序" + DuplicateNumber.modifyArrayDuplicate(array, length));
}
private static void printArray(int[] arr) {
System.out.print("array:\t" + arr.length + "\n");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
System.out.println();
}
}
题目2:不修改数组找出重复的数字
描述
在一个长度为n+1的数组里的所有数字都在1~n的范围内。
数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。
请找出数组中任意一个重复的数字,但不能修改输入的数组。
- 示例
输入:{2,3,5,4,3,2,6,7} 长度为8的数组
输出:2或者3 重复的数字
- 注意:
请勿将此题与上题联系
题目条件只是形似 但本质是不同的
上一题是:有n个元素,有n种元素 个数和种类相同 可能出现不重复的
本题是:有n+1个元素,有n种元素 个数和种类不同 一定会出现重复的鸽舍原理
思路
按区域元素出现频次,来辨别重复数字是否出现在该区域
鸽舍原理
n只鸽子,(n-1)个鸽舍,必有1个鸽舍有2只鸽子及以上
区域划分可以使用二分法
实现代码
class DuplicateNumber {
/**
* 不修改数组方法 1~n
* 鸽舍原理 + 二分查找 -- [1,3] 这4个数字在数组中出现5次,则必有一个数字出现两次及以上
*/
public static int binarySearchDuplicate(int[] arr, int length) {
if (arr.length <= 0 || length <= 0) {
return -1;
}
for (int i : arr) {
if (i < 0 || i > length - 1) {
return -1;
}
}
int start = 0;
int end = length - 1;
// 通过二分遍历元素种类 得到它在数组中出现的次数
while (end >= start) {
int middle = ((end - start) >> 1) + start;
int count = countRange(arr, 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;
}
/**
* 找出[start,end]在数组中出现的次数
*/
private static int countRange(int[] arr, int length, int start, int end) {
int count = 0;
for (int i : arr) {
if (i >= start && i <= end) {
count++;
}
}
return count;
}
}
- 测试
public class FindDuplicateNumber {
public static void main(String[] args) {
// 有多个重复数字
unitTest(new int[]{2,3,1,4,2,5,3});
// 有多个重复数字 偏后
unitTest(new int[]{1,2,3,4,5,5,1});
// 有一个重复数字 前后
unitTest(new int[]{1,2,3,4,5,6,1});
// 都是重复数字
unitTest(new int[]{2,2,3,3});
// 数组数字超出边界
unitTest(new int[]{2,3,7,0,2,5,3});
// 数组为空
unitTest(new int[]{});
}
private static void unitTest(int[] array) {
int length = array.length;
printArray(array);
System.out.println("二分查找" + DuplicateNumber.binarySearchDuplicate(array, length));
}
private static void printArray(int[] arr) {
System.out.print("array:\t" + arr.length + "\n");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
System.out.println();
}
}