题目描述:把一个数组最开始的若干个元素搬到数组的末尾,我们称之数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如:数组{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5}的一个旋转,该数组的最小值为1.
解题思路:题目的意思是在一个部分有序的数组中查找最小数字。
1. 利用输入的旋转数组特性,旋转之后的数组可分为两个有序的子数组。
2. 因为旋转是把最开始的若干元素搬到数组的末尾,因为输入数组是递增的,所以前面子数组的元素都 >= 后面子数组元素。
3. 用两个指针分别指向数组的第一个元素和最后一个元素,按照旋转规则,排除特例(特例是把排序数组的前面的0个元素搬到后面),第一个元素应该大于或者等于最后一个元素。
4. 找到数组中间的元素,中间元素是重点!
5. 如果中间元素位于前面的递增数组,那么它应该大于或者等于第一个指针指向的元素,此时数组中最小的元素应该位于中间元素的后面。我们可以把第一个指针指向该中间元素,缩小查找范围。
6. 如果中间元素位于后面的递增数组,那么它应该小于或者等于第二个指针指向的元素,此时数组中最小元素应该位于中间元素的前面。我们可以把第二个指针指向该中间元素,缩小查找范围。
7. 不管是移动第一个指针还是第二个指针,查找范围都会缩小到原来的一半。
8. 再用更新之后的指针重新一轮查找。
9. 当这两个指针相邻时,第二个指针指向的就是最小元素。即循环结束条件(point2 - point1 == 1)。
10. 返回中间下标值即为要查找的数字。
特殊情况:当两个指针指向的数字及它们的中间的数字三者相同的时候,我们无法判断中间的数字是位于前面的子数组还是后面的子数组。如:{0, 1, 1, 1, 1}的两个旋转->{1, 1, 1, 1, 0}和{1, 1, 1, 0, 1}。此时也就无法移动两个指针来缩小范围。判断有这种情况,我们不得不用顺序查找。
测试用例:
int main(){
//输入递增数组{1, 2, 3, 4, 5, 6}的一个旋转
int arr[6] = {3, 4, 5, 6, 1, 2};
//int arr[5] = {1, 1, 1, 0, 1}; //传入长度改为5, Output: 0
//保存最小数字
int min;
//查找其中的最小数字, 并返回结果
min = searchMin(arr, 6);
//输出结果
std::cout << "Minimum number is: " << min; //Output: 1.
return 0;
}
searchMin函数实现:
//考虑到有可能输入旋转数组是这样:{1, 0, 1, 1, 1}或{1, 1, 1, 0, 1},此时两个指针和中间数字的值都相同,只能采用
//顺序查找的方法
int minInOrder(int *arr, int index1, int index2){
int result = arr[index1]; //初始化为数组的第一个值
for(auto i = index1 + 1; i <= index2; ++i){
if(arr[i] < result)
result = arr[i];
}
return result;
}
//主体函数实现
int searchMin(int *arr, int length){
if(arr == NULL || length <= 0)
throw new std::exception("Invalid parameters!");
//两个指针(下标)
int index1 = 0, index2 = length - 1;
//中间下标,也是结果的下标
int indexMid = index1; //此处需要赋值其index1, 这样下面的循环不成立便会直接返回。
while(arr[index1] >= arr[index2]){ //由于是递增的旋转数组,前面子序列的值必然大于后子序列的值
if(index2 - index1 == 1){
indexMid = index2;//如果下标相邻,最小值就在index2
break;
}
//确定中间值
indexMid = (index1 + index2) / 2;
//作一判断,若三个下标相等,则只能采取顺序查找
if(arr[index1] == arr[index2] && arr[index1] == arr[indexMid])
return minInOrder(arr, index1, index2);
if(arr[indexMid] >= arr[index1]) //如果中间值大于前面的值,则代表中间值在前子序列,必然不是最小元素,则
index1 = indexMid; //令重新赋值index1跳到中间值, 缩小查找范围。
else if(arr[indexMid] <= arr[index2]) //如果中间值小于后面的值,则代表中间值在后子序列,有可能是最小元素,但
index2 = indexMid; //若不能达到退出循环的条件,必须继续缩小范围进行查找
}
return arr[indexMid];
}