一、题目描述:
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{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;
}