题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称为数组的旋转.输入一个递增排序的数组的一个旋
转,输出旋转数组的最小元素.例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的
最小值为1.
这道题的直观解法并不难,从头到尾遍历数组一次,我们就能找出最小的元素.这种思路的时间复杂度显然是
O(N).但是这个思路没有利用输入旋转数组的特性,肯定达不到面试官的要求.
我们注意到旋转之后的数组实际上可以划分为两个排序的子数组,而且前面的子数组的元素都大于或者等于后面
子数组的元素.我们还注意到最小的元素刚好是这两个子数组的分界线.在排序的过程中我们可以用二分查找实现
O(log n)的查找:找中间元素,让其跟元素首元素比较,如果大于首元素,则中间元素属于前半段有序数
组,如果小于尾元素,那么中间元素就是后半段的元素。依次查找
这样,和二分查找一样,我们用两个指针分别指向数组的第一个元素和最后一个元素.按照题目中的旋转的规\
则,第一个元素应该是大于或者等于最后一个元素(这其实并不完全对,对于特例,后面加以讨论)
接着我们可以找到数组中间的元素.如果该中间元素位于前面的递增子数组,那么它应该大于或者等于第一个指
针指向的元素.此时数组中最小的元素应该位于该中间元素的后面.我们可以把第一个指针指向该中间元素,这样可
以缩小寻找的范围.移动之后第一个指针仍然位于前面的递增子数组之中.
同样的,如果中间元素位于后面的递增子数组,那么它应该小于或者等于第二个指针指向的元素.此时该数组中
最小的元素应该位于该中间元素的前面.我们可以把第二个指针指向该中间元素,这样也可以缩小寻找的范围.移动
之后的第二个指针仍然位于后面的递增子数组之中.
不管移动第一个指针还是移动第二个指针,查找范围都会缩小到原来的一半.依次
按照上述思路,第一个指针总是指向前面递增数组的元素,而第二个指针总是指向后面递增数组的元素.最终第
一个指针指向前面数组的最后一个元素,而第二个指针指向后面子数组的第一个元素,也就算它们最终会指向两个相
邻的元素.而第二个指针也就刚好指向最小的元素.哈哈,循环结束.....
比如前面的数字:
此时两个指针的距离为1,表面第一个指针已经开始指向了第一个递增子数组的末尾,而第二个指针指向第二个
递增数组的开头.第二个字数组的第一个数字就是最小的数字,因此第二个指针指向的数字就是我们要查找的结果
#include <stdio.h>
int Min(int *numbers, int length)
{
if(numbers == NULL || length <= 0)
return -1;
int low = 0;
int high = length -1;
int middle;
while(numbers[low] >= numbers[high])
{
if(high - low == 1)
{
middle = high;
break;
}
middle = (low + high)/2;
if(numbers[middle] >= numbers[low])
low = middle;
else if(numbers[middle] <= numbers[high])
high = middle;
}
return numbers[middle];
}
int main()
{
int numbers[] = {2,3,4,5,1};
int length = sizeof(numbers)/sizeof(int);
printf("%d\n",Min(numbers, length));
}
运行结果:
前面我们提到在旋转数组中,由于是把递增排序数组前面的若干个数字搬到数组的后面,因此第一个数字总是
大于或者等于最后一个数字.但是,按照定义还有的一个特例:
如果把排序数组的前面的0个元素搬到最后面,即排序数组本身,这仍然是数组的一个逆转,我们的代码需要支持这
种情况.此时,数组中的第一个数字就算最小的数字,可以直接返回.因为数组中的第一个数字跟最后一个比较,如
果第一个小,那么就说明该数组是排序的,可以直接返回第一个数字了.
值得注意的是:当两个数相同的时候,并且中间的数字也相同的时候,我们把low移动到了middle的
位置,然后,我们也就理所应当的认为此时最小的数字位于中间数字的后面.是不是一定这样的呢???
从上图可以看出:第一个指针和第二个指针都是1,且中间的数字也是1
也就是说:最小值可能在左边,也可能在右边
当第一个和最后一个值相等的时候,中间的值或者为最小的,或者跟它们两个一样
如果:中间值小,那么就是我们要求的;如果中间值不小的话,那么我们就无法通过移动两个指针来缩小查找的范围
.我们就必须用顺序查找的方法了....