剑指offer之旋转数组的最小数字C++解法
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
首先,先排序再O(1)查找或者从头到尾遍历查找的办法肯定不是这道题要考察的关键。
在有序数组中,利用二分查找法可以实现O(logn)的查找。本题给出的数组在一定程度上是有序的,因此可以尝试使用二分法来查找最小元素。
二分法查找的原理: 分别定义三个指针low、high、mid,分别指向待查元素所在范围的下界和上界及区间的中间位置,即mid=(low+high)/2,让关键字与mid所指的数比较,若相等则查找成功并返回mid,若关键字小于mid所指的数则high=mid-1,否则low=mid+1,然后继续循环直到找到或找不到为止。
根据本题的情况,分别定义三个指针left,right,mid,分别指向待查元素的下界、上界和区间的中间位置,即mid=(left+high)/2,若left所指的数小于mid所指的数,说明mid位于前面的递增子数组,则left=mid,移动后left仍然位于前面的递增子数组;若right所指的数大于mid所指的数,说明mid位于后面的递增子数组,则right=mid,移动后right仍然位于后面的递增子数组;当right和left相遇时,right-left==1,即left指向前面递增子数组的最后一个数,right指向后面递增子数组的第一个数,这时候就找到了最小数,即right所指的数。但这种方法只解决了最常见的一种情况(即数组中不存在重复元素且搬到后面的元素个数大于0),还有几种特殊情况:
- 把前面0个元素元素搬到后面,这仍然是数组的一个旋转
- 数组中存在重复元素,如{1,0,1,1,1,},{1,1,1,0,1},这种情况下left、right、mid三个指针指向的数相等,无法判断mid所指是属于哪个子数组
对于第一个情况,即left所指小于right所指,这时旋转数组的第一个数即为目标的最小数,可以添加一个判断:当left所指小于right所指时,直接返回left所指数即可;
对于第二个情况,无法直接通过上面的二分法进行查找,可以考虑对这种情况下的数组进行整体遍历或者排序来进行查找,或者直接通过向右移动left指针来缩小二分查找的范围再进行查找,缩小范围之后同样有可能出现第一种情况,如果没有出现第一种情况,则按照普通情况进行处理,也将得道最终结果。
综上所述, 循环查找将存在两个出口,一个是按照普通情况循环N次后left和right相遇,跳出循环;另一种则是在特殊情况下遇到left指向的数小于right指向的数而跳出循环返回left指向的数。
所以,代码如下:
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
if (rotateArray.empty()) return 0;
int left = 0, right = rotateArray.size() - 1;
int mid = left;
while (rotateArray[left]>=rotateArray[right]) { //出口1
if(right-left==1){
mid = right;
break; //出口2
}
mid = (left+right) / 2; //这条语句要在出口2 后面,否则结果错误
if (rotateArray[left] < rotateArray[mid])
left = mid;
else if (rotateArray[mid] < rotateArray[right])
right = mid;
else {
++left; //缩小查找范围
}
}
return rotateArray[mid];
}
};