剑指OFFER笔记_03_2_不修改数组找出重复的数字_JAVA实现
- 题目:不修改数组找出重复的数字
- 题设条件中值得关注的点
- 解题思路
- 代码部分
- 第一种方法
- 函数主体部分
- 测试部分
- 测试运行结果
- 第二种方法
- 函数主体部分
- 测试部分
- 测试运行结果
题目:不修改数组找出重复的数字
- 在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数。例如,如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或者3。
题设条件中值得关注的点
- 数组长度为n+1,数字范围为1~n,也就是说n+1长度的数组中有n“种”数,必然有一个数是重复出现的,我们需要做的是找到它。
解题思路
- 第一种方法时间复杂度为O(n),空间复杂度为O(n),用空间换时间。
- 这种方法与题目01类似,创建一个与原数组长度相同为n+1的数组作为辅助,将原数组中的每个元素的作为下标存入辅助数组中,此时必然出现这种情况:试图存入一个数,但发现这个位置上已经有一个数了,此时该值就是重复的数字,返回该值。
- 第二种方法时间复杂度为O(nlogn),空间复杂度为O(1),用时间换空间。
- 这种方法的思想类似于二分查找。
- 假如n=7,数字范围为1~7,一共有8个数。start=1,end=7,middle=(1+7)/2=4。此时将范围缩小至1~4(二分)。
- 对1~4范围内的数进行计数,存于count,若出现了5次或以上,说明此区间存在重复的数(tips:但是不一定只存在于此区间),end=middle,开始新一轮检索。
- 否则start=middle+1,更改区间进行检索。
- 当出现start=middle的情况时,若此时计数结果count>1,则说明只有一个数的区间,出现了不止一次该数,显然该数重复出现,返回该数。若count<=1,返回0(无重复出现,若用例满足题设条件,不会出现该情况,但出于健壮性考虑,应当加上这一部分)。
- 此种方法有一个缺陷。比如在题设给的用例中,检测范围到了1~2的时候,count=2,理论上是没有重复的,但是实际上出现了两个2,没有1,不过由于缺少了1出现,所以除了2重复外,还有其他的重复元素,代码至少可以找到其他的重复的数字。此缺陷在此题要求中(只需要找到一个重复元素)可以接受,若更换条件要求,需要重新考虑更好的方法。
代码部分
第一种方法
函数主体部分
package q02;
/**
* 题目02
* 在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。
* 请找出数组中任意一个重复的数字,但不能修改输入的数组。
* 例如,如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或者3.
* @author asus
*
*/
public class Question02 {
/**
* 此函数用于得到重复出现的数字
* @param numbers 数组名称
* @param length 数组长度
* @return
*/
public static int getDuplication(int[] numbers, int length)
{
//异常处理
if(numbers == null || length <= 0)
{
return -1;
}
//元素范围不符合题目要求
for (int i = 0; i < numbers.length; i++)
{
if(numbers[i] <= 0 || numbers[i] >= length)
{
return -1;
}
}
//辅助数组
int[] buffer = new int[length];
//遍历numbers的所有元素
for (int i = 0; i < numbers.length; i++)
{
int temp=numbers[i];
//如果当前位空缺,就填入
if(buffer[temp] != temp)
{
buffer[temp] = temp;
}else //若不空缺,说明已经有元素填入,该元素就是重复元素
{
return temp;
}
}
//遍历完仍为找到重复元素,则说明没有重复,
return 0;
}
}
测试部分
package q02;
/**
* 对方法一的测试
* @author asus
*
*/
public class TestApp02 {
public static void main(String[] args) {
int[] numbers1 = {2,4,5,4,3,2,6,7};
int result1 = Question02.getDuplication(numbers1, numbers1.length);
System.out.println("result1: " + result1);
int[] numbers2 = null;
int result2 = Question02.getDuplication(numbers2, 0);
System.out.println("result2: " + result2);
int[] numbers3 = {5,1,2,3,5,4};
int result3 = Question02.getDuplication(numbers3, numbers3.length);
System.out.println("result3: " + result3);
}
}
测试运行结果
第二种方法
函数主体部分
package q02;
/**
* 题目02
* 在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。
* 请找出数组中任意一个重复的数字,但不能修改输入的数组。
* 例如,如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或者3.
* @author asus
*
*/
public class Question2 {
/**
* 此函数用于得到重复出现的数字,其中sout语句用于测试,已注释掉。
* @param numbers 数组名称
* @param length 数组长度
* @return
*/
public static int getDuplication(int[] numbers, int length)
{
if(numbers == null || length <= 0)
{
return -1;
}
//确定需要检索的数字范围
int start = 1;
int end = length-1;
//当范围>=0时反复检索
while(end >= start)
{
//范围分割
int middle = (start + end) / 2;
//System.out.println("start="+start+" middle="+middle);
//对出现在这个范围内的数字计数,如果count>end-start+1,则说明此范围内有重复出现的数字
int count = countRange(numbers, length, start, middle);
//System.out.println("count="+count);
//若start 和 end相遇
if(start == end)
{
//此时start==end,却出现超过1次,则重复出现了
if(count > 1)
{
return start;
}else //若start==end时还未出现重复,说明整个数组没有重复,所以返回0
{
break;
}
}
//若范围内出现次数超过范围大小,缩小范围继续搜索
if(count > middle - start + 1)
{
end = middle;
}else //否则将搜索范围修改至未检测部分
{
start = middle+1;
}
//System.out.println();
}
//此时已经退出while循环,证明没有重复数字,返回0
return 0;
}
/**
* 此函数用于计算该范围大小的数出现了多少次
* @param numbers 数组名称
* @param length 数组长度
* @param start 开始范围
* @param middle 结束范围
* @return 出现次数
*/
public static int countRange(int[] numbers, int length, int start, int middle)
{
//空数组中必然只能出现0次
if(numbers == null || length <= 0)
{
return 0;
}
//次数初始化为0
int count = 0;
//将整个数组遍历
for(int i = 0; i < length; i++)
{
//若处于此范围,count++
if(numbers[i] <= middle && numbers[i] >= start)
{
//System.out.println(numbers[i]+" in array.");
count++;
}
}
return count;
}
}
测试部分
package q02;
public class TestApp {
public static void main(String[] args) {
int[] numbers1 = {2,4,5,4,3,2,6,7};
int result1 = Question2.getDuplication(numbers1, numbers1.length);
System.out.println("result1: " + result1);
int[] numbers2 = null;
int result2 = Question2.getDuplication(numbers2, 0);
System.out.println("result2: " + result2);
int[] numbers3 = {5,1,2,3,5,4};
int result3 = Question2.getDuplication(numbers3, numbers3.length);
System.out.println("result3: " + result3);
}
}
测试运行结果
- 两种方式对于同样的测试用例,结果相同。