欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

剑指offer面试题(11)——旋转数组的最小数字

程序员文章站 2022-06-17 19:54:00
...

题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组 {3, 4, 5, 1, 2} 为 {1, 2, 3, 4, 5} 的一个旋转,该数组的最小值为 1 。

解题思路    

         最直观的做法是把旋转数组从前到后遍历一遍,其时间复杂度为 O(n)。很明显,这种解法效率较低。

        主要代码如下:

int FindMinElement(int* array,int length)
{
	int minElement = array[0];
	if (array == nullptr || length <= 0)
		throw new std::exception("Invalid parameters");
	for (int i = 0; i < length; ++i)
	{
		if (array[i] <= minElement)
			minElement = array[i];
	}
	return minElement;
}

        旋转后的数组实际为两个排序子数组的组合,在排序的数组中可以用二分查找的方法来查找最小数字,其时间复杂度为O(logn)。每次查找都把旋转数组平均分成两部分,通过比较当前旋转数组两端点和中间点的值,判断最小值在数组的哪一部分,从而达到缩小搜索范围的目的。具体过程如下图所示:

剑指offer面试题(11)——旋转数组的最小数字

剑指offer面试题(11)——旋转数组的最小数字

        需要注意的是,当旋转数组的两端点的值都与中间点的值相等时,无法判断最小值在哪一部分,因此需要采用顺序查找方法,其查找过程如下图所示:

剑指offer面试题(11)——旋转数组的最小数字

        上述查找过程主要实现代码如下:

int MinInOrder(int* array,const int& index1, const int& index2)
{
	int minNumber = array[index1];
	for (int i = index1 + 1; i <= index2; ++i)
	{
		if (array[i] < minNumber)
			minNumber = array[i];
	}
	return minNumber;
}
int FindMinElement(int* array, const int& length)
{
	//输入合法检查
	if (array == nullptr || length <= 0)
		throw new std::exception("Invalid parameters");
	int index1   = 0;
	int index2   = length-1;
	int midIndex = 0;
	//如果旋转数组前面的0个数字,则直接跳过,返回第一个元素
	while (array[index1] >= array[index2])
	{
		if (index2 - index1 == 1)
		{
			midIndex = index2;
			break;
		}
		midIndex = (index1 + index2) / 2;
		//如果index1,index2,midIndex三者指向的元素大小相等,
		//则只能顺序查找最小元素
		if (array[midIndex] == array[index1] && array[midIndex] <= array[index2])
			return MinInOrder(array,index1, index2);
		if (array[midIndex] >= array[index1])
			index1 = midIndex;
		else if(array[midIndex] <= array[index2])
			index2 = midIndex;

	}
	return array[midIndex];
}
        当题目要求在排序的数组或部分排序的数组中查找某一个数字或统计某个数字出现的次数时,都可以尝试使用二分查找方法。

测试用例1  

// ====================测试代码====================
void Test(int* array, int length, int expected)
{
	int result = 0;
	try
	{
		result = FindMinElement(array, length);

		for (int i = 0; i < length; ++i)
			cout << array[i] << endl;

		if (result == expected)
			cout << "\tpassed\n" << endl;
		else
			cout << "\tfailed\n" << endl;
	}
	catch (...)
	{
		if (array == nullptr)
			cout << "Test passed.\n" << endl;
		else
			cout << "Test failed.\n" << endl;
	}
}

int main()
{
	// 典型输入,单调升序的数组的一个旋转
	int array1[] = { 3, 4, 5, 1, 2 };
	Test(array1, sizeof(array1) / sizeof(int), 1);

	// 有重复数字,并且重复的数字刚好的最小的数字
	int array2[] = { 3, 4, 5, 1, 1, 2 };
	Test(array2, sizeof(array2) / sizeof(int), 1);

	// 有重复数字,但重复的数字不是第一个数字和最后一个数字
	int array3[] = { 3, 4, 5, 1, 2, 2 };
	Test(array3, sizeof(array3) / sizeof(int), 1);

	// 有重复的数字,并且重复的数字刚好是第一个数字和最后一个数字
	int array4[] = { 1, 0, 1, 1, 1 };
	Test(array4, sizeof(array4) / sizeof(int), 0);

	// 单调升序数组,旋转0个元素,也就是单调升序数组本身
	int array5[] = { 1, 2, 3, 4, 5 };
	Test(array5, sizeof(array5) / sizeof(int), 1);

	// 数组中只有一个数字
	int array6[] = { 2 };
	Test(array6, sizeof(array6) / sizeof(int), 2);

	// 输入nullptr
	Test(nullptr, 0, 0);

	system("pause");
	return 0;
}