《剑指offer》刷题——【数组】-<数组中重复的数字>
题目一、找出数组中重复的数字
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字
重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数
字2。
方法一:先排序,再扫描
- 先把输入的数组排序 O(nlogn)
- 然后从头到尾扫描
方法二:哈希表
- 从头到尾按顺序扫描数组的每个数组;
- 每扫描到一个数字的时候,都可以用O(1)判断哈希表里是否已经包含了该数字;
- 如果没有这个数字,就把他加入到哈希表中
- 如果哈希表已经存在该数字,就找到一个重复的数字
- 时间复杂度:O(n)
- 空间复杂度:O(n)
import java.util.Hashtable;
public class Solution {
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
// Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
// 这里要特别注意~返回任意重复的一个,赋值duplication[0]
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
public boolean duplicate(int numbers[],int length,int [] duplication) {
/**
* 借助Hashtable,遍历数组,若此值在hashtable中存在,则将此数组放入到结果集中
*/
if(numbers==null || length<=0)
return false;
Hashtable<Integer,Integer> table = new Hashtable<Integer, Integer>();
for(int i=0; i<length; i++){
if(table.containsKey(numbers[i])){
duplication[0] = numbers[i];
return true;
}else{
table.put(numbers[i],1);
}
}
return false;
}
}
方法三:数组重排
- 数组中的数字都在【0~n-】范围内,如果这个数组中没有重复的数字,那么当数组排序之后数字 i 将出现在下标为 i 的位置。由于数组中有重复的数字,那么有些位置可能存在多个数字,有些位置可能没有数字
- 利用上述规律,进行数组重排
- 遍历数组中每一个数字,当遍历到第 i 个数字时,比较第 i 个数字(表示为 x)是否等于 i
- 如果是,则遍历下一个数字;
- 如果不是,将第 i 个数字与第 x 数字比较,若与第 x 数字相等,就找到一个重复的数字(因为 i x 位置都出现了);
- 如果与第 x 个数字不相等,就把第 i 个数字和第m个数字交换,把m放到属于它的位置
- 重复比较,交换过程,直至找到一个重复的数字位置
- 时间复杂度O(n)
- 空间复杂度O(1)
注:一维数组在内存中占据连续的空间,因此可以根据下标定位对应的元素。
import java.util.Hashtable;
public class Solution {
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
// Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
// 这里要特别注意~返回任意重复的一个,赋值duplication[0]
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
public boolean duplicate(int numbers[],int length,int [] duplication) {
//1. 判断是否分配内存 是否长度不为空
if(numbers==null || length<=0)
return false;
//2. 检查所有数字是否在【0~n-1】之间
for(int i=0; i<length; i++){
if(numbers[i]<0 || numbers[i]>length-1){
return false;
}
}
//3. 遍历每个数字
for(int i=0; i<length-1; i++){
//是否第i个数字等于i,是则进行下一数字,不是则需要进一步判断第i个数字m是否和第m个数字相等
while(numbers[i] != i){
//第i个数字是否等于第m个数字
if(numbers[i] == numbers[numbers[i]]){
duplication[0]=numbers[i];
return true;
}
//第i个数字和第m个数字交换
int temp = numbers[i];
numbers[i] = numbers[temp];
numbers[temp] = temp;
}
}
return false;
}
}
题目二、不修改数组找出重复的数字
在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不
能修改输入的数组。例如:如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或者3
方法一:和上一题方法三同
- 创建一个长度为n+1的辅助数组;
- 然后将原数组中的每个数字复制到辅助数组中
- 然后重复方法三的过程
- 此方案需要额外的辅助空间O(n)
方法二:二分查找
- 假设没有重复的数字,那么在1~n的范围内只有n个数字。然而数组中包含超过n个数字,所以一定包含了重复的数字
- 利用上述规律,结合二分查找,解决此问题
- 把1~n的数字从中间的数字m分为两部分,前面一半为 1~m ,后面一半为m+1~ n
- 如果1~m的数字在整个数组中出现过的次数超过m,那么这一半的区间里一定包含重复的数字;否则另一半中一定包含重复的数字
- 继续包含重复数字的区间一分为二,直到找到一个重复的数字
- 时间复杂度:O(nlogn)
- 空间复杂度:O(1)
注意:这种算法不能保证找出所有重复的数字
package array_question;
/**
* @author hw
* @version 0.1
* @description 不修改数组找出重复的数字
* @date 2019/4/15
*/
public class TestFindDuplication {
//找出重复的数字
public int getDuplication(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 start = 1;
int end = arr.length-1;
while (end>=start){
int mid = ((end-start)>>1)+start;
int count = countRange(arr,start,mid);
//两个元素,1-1范围
if(end==start){
if (count > 1)
return start;
else
break;
}
if (count>(mid-start+1))
end = mid;
else
start = mid+1;
}
return -1;
}
public int countRange(int[] arr, int start, int end){
int count = 0;
if (arr == null)
return 0;
for (int i = 0; i < arr.length; i++) {
if (arr[i]>=start && arr[i]<=end)
++count;
}
return count;
}
/*********************************************测试****************************************/
public static void main(String[] args) {
TestFindDuplication t = new TestFindDuplication();
int[] a = null;
int[] b = {1,2,3,4};
int[] c = { 1, 2, 3, 2, 4 };
int dup1 = t.getDuplication(a);
int dup2 = t.getDuplication(b);
int dup3 = t.getDuplication(c);
if (dup1 >= 0)
System.out.println("重复数字为:" + dup1);
if (dup2 >= 0)
System.out.println("重复数字为:" + dup2);
if (dup3 >= 0)
System.out.println("重复数字为:" + dup3);
}
}
运行结果: