求旋转数组中的最小数字

1、题目:

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

2、解题思路:

直观解法:从头遍历数组一次,找出最小的元素。时间复杂度是O(n),但是这种思路没有利用输入的旋转数组这个特性。

我们注意到旋转之后的数组其实是两个排序的子数组。而且最小的元素恰好是这两个子数组的分界线。这样我们可以考虑使用二分查找法实现O(logn)。

和二分查找法一样,我们用两个指针(index1、index2)分别指向数组的第一个元素和最后一个元素。

接着我们找到数组中间的元素。如果该中间元素大于或者等于第一个指针(index1)指向的元素,则数组中最小的元素应该位于该中间元素的后面,我们就可以把第一个指针指向该中间元素,这样可以缩小寻找的范围。

同样,如果该中间元素小于或者等于第二个指针(index2)指向的元素,则表明该数组中最小的元素应该位于该中间元素的前面。所以我们可以把第二个指针指向该中间元素,缩小下一步寻找范围。

不管是移动第一个指针还是第二个指针,查找范围都会缩小到原来的一半。接下来我们再更新之后的两个指针,重复做新一轮的查找。

按照上述思路,第一个指针总是指向前面递增数组的元素,而第二个指针总是指向后面递增数组的元素。最终第一个指针将指向前面子数组的最后一个元素,而第二个指针会指向后面子数组的第一个元素。也就是说他们最终会指向两个相邻的元素,而第二个指针指向的刚好是最小的元素。这就是循环结束的条件

3、实现

基于上面思路,写出实现代码

//求旋转数组中的最小数字
int Min(int *DataArr,int length)
{
	if(DataArr == NULL || length<=0)
		throw new std::exception("Invalid parameters");
	int index1=0;
	int index2=length-1;
	int mid=index1;
	while(DataArr[index1]>=DataArr[index2])
	{
		//如果两个指针相邻了,说明已经找到最小元素位置
		if(index2-index1 == 1)
		{
				mid=index2;
				break;
		}
		mid=(index1+index2)/2;
		if(DataArr[mid] >= DataArr[index1])
		{
			index1=mid;
		}
		else if(DataArr[mid] <=DataArr[index2])
		{
			index2=mid;
		}
	}
	return DataArr[mid];
}
int main()
{
	//求旋转数组中的最小元素
	cout<<endl<<"求旋转数组中的最小元素"<<endl;
	const int length=9;
	int a[length]={5,6,7,8,9,1,2,3,4};
	int min=Min(a,length);
	cout<<"min="<<min<<endl;
	system("pause");
	return 0;
} 

4、改进

1、考虑旋转数组的特例,:如果把排序数组的前面的0个元素搬到最后面,即排序数组本身,这仍然是数组的一个旋转。此时数组中的第一个数字就是最小的数字,可以直接返回。这就是我们在代码中把mid初始化为index1的原因。

2、考虑另外一种情况:当index1和index2所对应的数字相同且中间的数字也相同时,如何确定idnex1和index2的值。


看一个例子:数组{1,0,1,1,1}和数组{1,1,1,0,1}都可以看成是递增排序数{0,1,1,1,1}的旋转数组,然而在这两个数组中,第一个数字,最后一个数字和中间数字都是1,我们无法确定中间的数字1是属于第一个递增子数组还是属于第二个递增子数组。因此,当两个指针的数字及他们中间的数字三者相同的时候,我们无法判断中间的数字是位于前面的子数组还是后面的子数组,也就无法移动两个指针来缩小查找的范围。此时,我们不得不采用顺序查找的方法。

改进代码:

//求旋转数组中的最小数字
int MinInOrder(int *DataArr,int index1,int index2);
int Min(int *DataArr,int length)
{
	if(DataArr == NULL || length<=0)
		throw new std::exception("Invalid parameters");
	int index1=0;
	int index2=length-1;
	int mid=index1;
	while(DataArr[index1]>=DataArr[index2])
	{
		//如果两个指针相邻了,说明已经找到最小元素位置
		if(index2-index1 == 1)
		{
				mid=index2;
				break;
		}
		mid=(index1+index2)/2;
		//如果index1、index2和mid指向的三个数字相等,则只能顺序查找
		if(DataArr[index1] == DataArr[index2] 
		&& DataArr[mid] == DataArr[index1])
			return MinInOrder(DataArr,index1,index2);
		if(DataArr[mid] >= DataArr[index1])
		{
			index1=mid;
		}
		else if(DataArr[mid] <=DataArr[index2])
		{
			index2=mid;
		}
	}
	return DataArr[mid];
}
//顺序查找
int MinInOrder(int *DataArr,int index1,int index2)
{
	int result = DataArr[index1];
	for(int i=index1+1;i<=index2;i++)
	{
		if(result>DataArr[i])
			result=DataArr[i];
	}
	return result;
}
int main()
{
	//求旋转数组中的最小元素
	cout<<endl<<"求旋转数组中的最小元素"<<endl;
	const int length=9;
	int a[length]={5,6,7,8,9,1,2,3,4};
	int min=Min(a,length);
	cout<<"min="<<min<<endl;
	system("pause");
	return 0;
} 


原文地址:http://www.zhujun.me/array_min_number.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值