思路: 如序列 3,4,5,1,2 观察到数组旋转后可以分为两个部分,两个排序的子数组,而且前面的元素都不小于后面数组的元素。而且最小值元素刚好是这两个数组的分界线。
在有序数组中可以使用二分查找法实现O(logn)的查找。本题的数组一定程度上也是有序的,因此可以使用二分法的思想来寻找这个最小元素。
如上图所示,a中:array[mid] >= array[p2]说明mid所指还在左边序列中,p1 = mid;
b中:array[mid] >= array[p2]不成立,则说明mid所指已经在右边序列中,p2 = mid;
c中 p2 - p1 == 1 已经到达分界线即上一次的mid位置
特殊情况:有序数组前面0个元素后移,如 1,2,3,4,5 代码应该要考虑这种情况,因此初始化 mid的时候 将mid初始化为0,如果不执行循环则直接返回array[mid].
超特殊情况:
1 | 0 | 1 | 1 | 1 |
这种情况下 array[p1] = array[p2] = array[mid] 无法判断mid所指属于左右哪个序列,这个时候不得不采取顺序查找的方式。 这个情况是本题的第二个关键。第一个是二分思想。
牛客网版本代码:
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
int p = 0;
int q = rotateArray.size() - 1;
int mid = 0; //初始化为0,防止首元素即为最小值
while(rotateArray[p] >= rotateArray[q])
{
//分界点
if(q - p == 1)
{
mid = q;
break;
}
mid = (p + q) / 2;
//元素一样无法二分查找,顺序查找
if(rotateArray[p] == rotateArray[mid] &&
rotateArray[q] == rotateArray[mid])
return minInOrder(rotateArray, p, q);
if(rotateArray[mid] >= rotateArray[q])
p = mid;
else
q = mid;
}
return rotateArray[mid];
}
//顺序查找最小元素,当序列如1 0 1 1 1时二分查找失效之时
int minInOrder(vector<int> Array, int low, int high)
{
int min = Array[low];
for(int i = low + 1; i <= high; ++i)
if(Array[i] < min)
min = Array[i];
return min;
}
};
有序数组本身就是旋转的特例。另外还要考虑数组中有相同数字的特例。如果不能很好地处理这些特例,就很难写出让面试官满意的代码。