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;
}