问题:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转,输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素,例如数组{3,4,5,1,2}为数组{1,2,3,4,5}的一个旋转,该数组的最小值为1。
解题思路:一个递增数组经旋转后分为两部分,它们都是递增的数组,第一个子数组的第一个元素不小于第二个子数组的最后一个元素,我们要寻找的是第一个子数组最后一个元素的下一个元素或第二个子数组的第一个元素。利用二分查找法,设有两个指针分别指向旋转数组的第一个和最后一个元素,设为left和right,找到两个指针的中间元素mid,如果它不小于第一个元素,则说明这个中间元素位于第一个递增子序列,我们要找的元素在这个中间元素的后面,此时将指向第一个元素指针left置为mid;如果它不大于最后一个元素,则说明这个中间元素位于第二个递增子序列,我们要找的元素在这个中间元素的前面,此时将指向最后一个元素的指针right置为mid。接下来递归调用该函数,最后left将指向第一个递增子序列的最后一个元素,right将指向第二个递增子序列的第一个元素,也即我们要寻找的元素,此时right-left==1。
根据上述解题思路写出的代码如下:
int Min(int* numbers,int length){
int left = 0;
int right = length-1;
while(right-left>1){
int mid = (left+right)/2;
if(numbers[mid] >= numbers[left])
left = mid;
if(numbers[mid] <= numbers[right])
right = mid;
}
return numbers[right];
}
但是上述写出的代码在某些情况下会遇到问题,如果该旋转数组是由原递增数组将最开始的0个元素移到数组末尾,那么将出现问题,如{1,2,3,4,5}找到的将是元素3。问题在于此时第一个子数组的第一个元素小于第二个子数组的最后一个元素,所以我们要对代码进行修改,修改后的代码如下:
int Min(int* numbers,int length){
int left = 0;
int right = length-1;
int mid = left;
while(numbers[left] >= numbers[right]){
if(right-left == 1){
mid = right;
break;
}
int mid = (left+right)/2;
if(numbers[mid] >= numbers[left])
left = mid;
if(numbers[mid] <= numbers[right])
right = mid;
}
//注意此时返回的是最中间元素的值,而不是numbers[right],是为了兼容第一个元素时最小值的情况
return numbers[mid];
}
继续寻找特殊情况,我们发现如果是类似于如下的数组{1,0,1,1,1}或{1,1,1,0,1},第一个元素、最后一个元素、中间元素均相等,此时我们无法判断中间的元素时位于第一递增子序列({1,1,1,0,1})还是第二递增子序列({1,0,1,1,1}),此时我们只能遍历该序列从中找到最小值。修改后的代码如下:
int Min(int* numbers,int length){
int left = 0;
int right = length-1;
int mid = left;
while(numbers[left] >= numbers[right]){
if(right-left == 1){
mid = right;
break;
}
int mid = (left+right)/2;
if(numbers[mid] == numbers[left] && numbers[mid] == numbers[right]){
return finMin(numbers,left,right);
}
if(numbers[mid] >= numbers[left])
left = mid;
if(numbers[mid] <= numbers[right])
right = mid;
}
return numbers[mid];
}
int findMid(int* numbers,int left,int right){
int min = numbers[left];
for(int i = left+1; i <= right; i++){
if(numbers[i] < min)
min = numbers[i];
}
return min;
}