《剑指Offer》面试题3

 

问题一、找出数组中重复的数字
  

题目:在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,
也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2, 3, 1, 0, 2, 5, 3},
那么对应的输出是重复的数字2或者3。

 

分析:数组的长度(n)等于数字的范围(0~n-1),如果使用数组下标来定位数组中数字的话,会出现两种情况

没有重复数字:每一个位置刚好对应一个数字,排序后的数组元素的值和下标一一对应

有重复数字:有些位置可能会出现多个数字,有些位置可能没有数字

 

算法思想:重排数组,把元素放置在指定的位置。通过遍历数组的每一个元素,

如果数组元素的值等于下标值,继续遍历下一个元素
如果数组元素的值不等于下标值,根据元素的值找到对应的位置,进行比较,再根据比较结果判断
    比较值相等,找出重复数字,返回
    比较值不等,进行位置交换
继续判断数组元素的值....

 

其他方法:

1.对数组排序,在已排序的数组中扫描重复数字

2.创建哈希表,通过判断新加入的数字在哈希表中是否已存在,来判断重复数字

 

代码

#include "iostream"
using namespace std;


//函数功能:寻找数组中的任意重复数字
//输入参数:numbers[] 源数组、length 数组长度、duplication 重复元素
//返回值: false 没有重复数字、true发现重复数字
bool duplicate(int numbers[], int length, int* duplication)
{
	
	//判断无效参数
	if ( (numbers == nullptr) || (length < 0) )
	{
		return false;
	}

	//判断元素数是否满足
	for (int i = 0; i < length; i++)
	{
		if ( (numbers[i] < 0) || (numbers[i] > length-1) )
		{
			return false;	
		}
	}


	for (int i = 0; i < length; i++)
	{//1.遍历数组
		
		while(i != numbers[i])
		{//2.判断当前值是否等于下标值
			if (numbers[i] == numbers[numbers[i]])
			{//3.判断当前值是否已放置下标位置(判断存在)
				*duplication = numbers[i];	//计入重复元素
				return true;		//4.已存在,返回结果
			}

			//5.交换当前值到下标位置
			int temp = numbers[i];
			numbers[i] = numbers[temp];
			numbers[temp] = temp;
		}
	}
	return false;	//没有找到
}


//进行测试
void test01()
{
	int numbers[] = {2, 3, 1, 0, 2, 5, 3};
	int duplication = 0;
	if ( duplicate(numbers, sizeof(numbers)/sizeof(int), &duplication) == true )
	{
		cout << "res:" << duplication << endl;
	}	
}
int main(int argc, char const *argv[])
{
	test01();
	return 0;
}

运行

 

 

问题二、不修改数组找出重复的数字


题目:在一个长度为n+1的数组里的所有数字都在1到n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。例如,如果输入长度为8的数组{2, 3, 5, 4, 3, 2, 6, 7},那么对应的输出是重复的数字2或者3。

 

分析:数组长度(n+1)大于数字的范围,所以一定有重复的数字。使用二分法的思路,将不断小查找区间的范围,对重复数字进行查找。

 

其他方法:创建一个辅助数组,在辅助数组中查找重复数字,不会破坏原数组的结构。

 

代码

#include "iostream"

using namespace std;

int countRange(const int* numbers, int length, int start, int end);	//统计次数
int getDuplication(const int* numbers, int length);					//查找任意重复数字

//功能:(已知)数组内有重复数字,查找重复数字
//输入:numbers源数组(数字范围1-n)、length数组长度(n+1)
//返回:重复的数字
int getDuplication(const int* numbers, int length)
{
	if ( (numbers == nullptr) || (length <= 0) )
	{//无效参数
		return -1;
	}

	int start = 1;
	int end = length - 1;

	while(start <= end)
	{
		//1.将数字序列所在区间进行二分
		int middle = (end - start)/2 + start;

		//2.统计区间范围的数字的出现次数
		int count = countRange(numbers, length, start, middle);

		//3.判断区间长度是否为1,找出重复数字
		if (start == end)
		{
			if (count > 1)
			{
				return start;
			}
			else
			{
				break;
			}
		}

		//4.重新调整区间,区间内包含重复数字
		if ( count > ((middle - start) + 1) )
		{
			end = middle;
			// end = middle - 1;
		}
		else
		{
			start = middle + 1;
		}

	} 
	return -1;

}


//功能:统计start-end范围的数字的出现次数
//输入:numbers源数组、length数组长度、start,end表示数字范围
int countRange(const int* numbers, int length, int start, int end)
{
	if ( (numbers == nullptr) || (length <= 0) )
	{//无效参数
		return 0;
	}

	int count = 0;
	for (int i = 0; i < length; i++)
	{//遍历数组元素
		if ( (start <= numbers[i]) && (numbers[i] <= end) )
		{
			count++;	//统计次数
		}
	}
	return count;
}

void test01()
{
	int arr[] = {2, 3, 5, 4, 3, 2, 6, 7};
	int res = getDuplication(arr, sizeof(arr)/sizeof(int));
	cout << "res:" << res << endl;
}

int main(int argc, char const *argv[])
{
	test01();
	return 0;
}

 

运行

 

 

总结:面试官提出的需求不同,最终所采用的算法也不相同,这说明面试中和面试官的交流的重要性!!

 

我的码云https://gitee.com/hinzer/sword_to_offer/tree/master/questions