把一个数组最开始的若干个元素搬到数组末尾,称为数组旋转。输入一个递增排序的数组的一个旋转,输出该旋转数组的最小元素。如递增数组为[1,2,3,4,5],旋转数组为[3,4,5,1,2]。
分析:
二分查找。首先设置头尾指针,一般的头元素大于等于尾元素。然后找到中间位置的数字。
- 若中间位置数字比头元素大,说明最小值在后半段。将头指针移动到中间位置二分;
- 若中间位置比尾元素小(肯定也<=头元素),说明最小元素在前半段,将尾指针移动到中间位置二分。
最终结果:头尾指针相邻。尾指针为最小值。头指针走到前半段的最大值,尾指针走到后半段的最小值。
特例1:若移动为0,则数组仍为增序数组,第一个为最小元素。while的条件是头指针>=尾指针元素。此时则破不进while。所以将indexMid初始化为index1,可保证特例满足。
特例2:较为复杂,一般想不到这个反例,如下示。
exception : [0,1,1,1,1] -> [1,0,1,1,1] / [1,1,1,0,1]
cannot decide how to partition
上述逻辑在于当中间值>=头元素时,默认将头指针移到中间位置。特例2不满足。原因在于此时无法判断最小值在前半段还是后半段。因此只能顺序遍历找到最小值。
代码:
// offer-8-rota_array_minNumber.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
using namespace std;
int MinInorder(int * array, int index1, int index2);
int Min(int * array, int length)
{
if (array == nullptr || length <= 0)
throw new exception("Invalid Params");
int index1 = 0;
int index2 = length-1;
//int indexMid;
int indexMid = index1;
// if rotation is 0, the first is the min
// while end
// so (indexMin = index1) will handle
while(array[index1] >= array[index2])
{
if(index2 - index1 == 1)
{
indexMid = index2;
break;
}
indexMid = (index1+index2)/2;
// if three numbers is equal, search in order
// exception : [0,1,1,1,1] -> [1,0,1,1,1] / [1,1,1,0,1]
// cannot decide how to partition
if(array[index1] == array[index2] && array[index2] == array[indexMid])
return MinInorder(array, index1, index2);
// 除了特例2,一般都可走此路
if(array[indexMid]>=array[index1])
index1 = indexMid;
else if (array[indexMid] <= array[index2])
index2 = indexMid;
}
return array[indexMid];
}
// 顺序遍历找到最小值
int MinInorder(int * array, int index1, int index2)
{
int result = array[index1];
for(int i = index1+1; i<=index2; ++i)
{
if(result > array[i])
result = array[i];
}
return result;
}
int _tmain(int argc, _TCHAR* argv[])
{
int a[] = {1,1,1,0,1};
cout << Min(a, 5) << endl;
return 0;
}