旋转数组中最小数字

本文介绍了如何解决旋转排序数组中找最小值的问题。通过分析题目,利用二分查找法,可以在O(logn)的时间复杂度内找到最小值。在某些特殊情况如三个连续相同的数字,可能需要退化到顺序查找。并提供了详细的解题思路和示例。
摘要由CSDN通过智能技术生成

一、题目描述:

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


二、题目分析:

       这道题最直观的解法即从头到尾遍历一遍数组,我们就能找出最小的元素。但这种方法的时间复杂度显然是O(n)。而且这种方法也没有利用旋转数组和数组元素排序的特性。

       我们注意到旋转之后的数组实际上可以划分为两个排序的子数组,而且这两个数组有以下特点:

      (1)、前面的子数组的元素都大于或等于后面子数组的元素。

      (2)、最小的元素刚好是这两个子数组的分界线。

       在排序的数组中我们可以用二分查找法实现O(logn)的查找。本题给出的数组在一定程度上是排序的,因此我们可以尝试用二分查找法的思路来寻找这个最小的元素。


三、解题思路:

      (1)、根据二分法,我们可以找到数组的中间元素。


      (2)、如果该中间元素位于前面的递增子数组,那么它应该大于或者等于第一个指针指向的元素。此时数组中最小的元素应该位于该中间元素的后面。我们可以把第一个指针指向该中间元素,这样可以缩小寻找的范围。移动之后的第一个指针仍然位于前面的递增子数组之中


      (3)、如果该中间元素位于后面的递增子数组,同理,它应该小于或者等于第二个指针指向的元素。此时该数组中的最小的元素应该位于该中间元素的前面。我们可以把第二个指针指向该中间元素,这样亦可以缩小寻找的范围。移动之后的第二个指针仍然位于后面的递增子数组之中


      (4)、这样,第一个指针总是指向前面递增数组的元素,而第二个指针总是指向后面递增数组的元素。最终第一个指针将指向前面子数组的最后一个元素,而第二个指针会指向后面子数组的第一个元素。也就是它们最终会指向两个相邻的元素,而第二个指针指向的刚好是最小的元素。这就是循环结束的条件。


       我们以题目中的{3, 4, 5, 1, 2}为例,如图所示:



           最后,我们还应考虑一种情况:

           例如:数组{1, 0, 1, 1, 1}和数组{1, 1, 1, 0, 1}都可以是递增排序数组{0, 1, 1, 1, 1}的旋转。

           注意:在这两个数组中,第一个数字、最后一个数字和中间的数字都是1,我们无法确定中间的数字1属于第一个递增子数组还是属于第二个递增子数组。第二个子数组用灰色背景表示。


           因此,当两个指针指向的数字及它们中间的数字三者相同的时候,我们无法判断中间的数字是位于前面的子数组还是后面的子数组中,也就无法移动两个指针来缩小查找的范围。此时,我们不得不采用顺序查找的方法。


四、源代码:


#include <iostream>
using namespace std;

//顺序从numbers数组的index1下标到index2下标之间查找最小元素值
int MinInOrder(int *numbers, int index1, int index2)
{
	int result = numbers[index1];

	for(int i = index1 + 1; i <= index2; i++)
	{
		if(result > numbers[i])
			result = numbers[i];
	}

	return result;
}


/*
 *函数功能: 查找旋转数组的最小值元素
 *参数说明: @numbers: 数组指针
 *          @length:  数组长度
 *返回值  : 找到的最小值元素
 */
int MinNumber(int *numbers, int length)
{
	//检查参数合法性
	if(numbers == NULL || length <= 0)
		throw "Invalid parameters";

	//二分法三个指针
	int index1 = 0;
	int index2 = length - 1;
	int indexMid = index1;

	while(numbers[index1] >= numbers[index2])
	{
		/*如果numbers[index1]大于numbers[index2],说明index1在第一个子数组中,
		 *index2在第二个子数组中。如果两个位置相邻,说明index2是第一个数组的
		 *最后一个元素(即整个数组的最大值),index1则是第二个数组的第一个元素
		 *(即整个数组的最小值)
		 */
		if(index2 - index1 == 1)
		{
			indexMid = index2;
			break;
		}

		indexMid = (index1 + index2) / 2;

		//如果下标index1、index2和indexMid指向的三个数字相等,只能顺序查找
		if(numbers[index1] == numbers[index2]
			&& numbers[indexMid] == numbers[index1])
		{
			//在numbers数组的index1下标和index2下标之间查找最小值
			return MinInOrder(numbers, index1, index2);
		}

		//indexMid处于第一个数组中时,index1向indexMid处收缩
		if(numbers[indexMid] >= numbers[index1])
			index1 = indexMid;
		//indexMid处于第二个数组中时,index2向indexMid处收缩
		else if(numbers[indexMid] <= numbers[index2])
			index2 = indexMid;
	}

	return numbers[indexMid];
}


int main(void)
{
	int nums1[] = {3, 4, 5, 1, 2};
	int nums2[] = {1, 0, 1, 1, 1};

	printf("nums1 中的最小值为: %d\n", MinNumber(nums1, sizeof(nums1)/sizeof(nums1[0])));
	printf("nums2 中的最小值为: %d\n", MinNumber(nums2, sizeof(nums2)/sizeof(nums2[0])));
	
	return 0;
}

五、运行截图:

                

文章源自剑指Offer!



       

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值