剑指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=1end=7middle=(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);
	}
}

测试运行结果

在这里插入图片描述

  • 两种方式对于同样的测试用例,结果相同。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值