问答题
将一个数组最开始的若干个元素搬到数组的末尾,称之为数组的旋转。输入一个已排好序数组的一个旋转,求该旋转数组的最小元素。如,数组{3,4,5,1,2}为有序数组{1,2,3,4,5}的一个旋转数组,该数组的最小值为1。
(1)给出算法的基本设计思想。
(2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
(3)说明你所设计算法的时间复杂度和空间复杂度。
【正确答案】
【答案解析】(1)算法的基本设计思想:
注意到旋转之后的数组实际上可以划分成两个排序的子数组,且前面的子数组的元素都大于或等于后面子数组的元素,而最小的元素刚好是这两个子数组的分界线。
我们试着用二元查找的思路寻找这个最小的元素:
定义两个指针,分别指向数组的第一个元素和最后一个元素。按照题目旋转的规则,第一个元素应该是大于或等于最后一个元素的。再定义一个指针指向中间的元素,如果该中间元素位于前面的递增子数组,那么它应大于或等于第一个指针指向的元素,此时最小的元素位于右子数组,然后把第一指针指向该中间元素,这样可以在缩小的范围内继续寻找。同样,如果该中间元素位于后面的递增子数组,思路和上面是类似的。
按照上述思路,第一个指针总是指向前面递增数组的元素,而第二个指针总是指向后面递增数组的元素。最后,第一个指针将指向前面子数组的最后一个元素,而第二个指针会指向后面子数组的第一个元素,此时两个指针相邻,而第二个指针指向的正好是最小元素。这就是循环结束的条件。
(2)算法的实现如下:
int Min(int *numbers, int length){
if(numbers==0||length<=0)
return 0;
int index1=O; //第一个指针
int index2=length-1; //第二个指针
int indexMid=index1; //中间指针
while(numbers[index1]>=numbers[index2]){
if(index2-index1==1){
indexMid=index2;
break;
}
indexMid=(index1+index2)/2;
if(numbers[indexMid]>=numbems[index1]) //在右区间
index1=indexMid;
else if(numbers[indexMid]<=numbers[index2]) //在左区间
index2=indexMid;
return numbers[indexMid];
}
每次都把寻找的范围缩小了一半,时间复杂度为O(log
2
N)、空间复杂度为O(1)。
解法二:本题最直观的解法并不难。从头到尾遍历数组一次,就能找出最小元素,时间复杂度显然是O(N)。但这个思路没有利用输入数组的特性。