题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
解决思路
最简单的思路,把它看作一个普通的数组,直接遍历求解,是一个O(n)级别时间。复杂度
//运行时间:32ms
//运行内存:692K
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
if(rotateArray.size()==0) return 0;
int min = rotateArray[0];
for(int i=0;i<rotateArray.size();i++)
if(min>rotateArray[i]) min=rotateArray[i];
return min;
}
};
但是,很显然,这不是最佳的解法,我们没有充分利用旋转数组的这个条件,如何最大限度的活用这个旋转数组的条件,是我们这道题解题的关键。
思路一:二分法查找
这里我们很快会想到,一种活用单调递增顺序的元素查找方法–二分查找法。
虽然这道题不是严格的单调递增序列,但是我们这里仍然可以借用二分查找法的思想。
我们设置left,mid,right分别指向旋转数组最左边,最右边的数字。
(1)当rotateArray[mid]>rotateArray[right],说明最小值一定在[mid+1,right]上
(2)当rotateArray[mid]<rotateArray[right],说明最小值一定在[left,mid]上
(3) 当这两者相等时,我们没有办法,只能顺序搜索。
//运行时间:36ms
//运行内存:620K
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
int length = rotateArray.size();
if(length == 0) return 0;
int left = 0;
int right =length -1;
int mid;
while(left<right)
{
mid = left +(right-left)/2;//比(right+left)/2的好处在于防止整型溢出
if(rotateArray[mid]>rotateArray[right])
left = mid+1;//这里可以加一是因为至少存在最右的数字比中间数小,所以可以排除
else if(rotateArray[mid]<rotateArray[right])
right = mid;//千万注意这里不减1是因为可能出现中间值就是最小值的情况
//如果这里减1了,通过用率只有15%
else return searchMin(rotateArray,mid,right);
}
return rotateArray[right];
}
private:
int searchMin(vector<int> &num,int left,int right)
{ int min = num[left];
while(left<right)
{
if(min>num[left]) min=num[left];
left++;
}
return min;
}
};
特别注意与二分查找法有两个细微的不一样的地方。
1.在于第二个条件的判断区间不能减1。具体的原因代码中已经详细阐明,这里不再赘述。
2.另外一定要注意我们的while循环条件一定是(left<right),而不是小于等于。
原因是因为,我们循环停止的时候,一定是当指针left和right同时指向那个最小的数字时,这也是我们代码里注解了为什么可以返回left的值,也可以返回right的值。
如果我们在循环条件上是等于,那么在等于的时候会继续循环,然后会发现left=mid=right,然后会执行搜索,造成不必要的浪费。
小结
这道题一定要思路清晰,分析问题,抓住可以用二分法思路的关键。
参考文献
《剑指offer》
牛课网刷题链接link.