题目要求:
有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。
数据范围:1≤n≤100001 \le n \le 100001≤n≤10000,数组中任意元素的值: 0≤val≤100000 \le val \le 100000≤val≤10000
要求:空间复杂度:O(1) ,时间复杂度:O(logn)
牛客链接:https://www.nowcoder.com/practice/9f3231a991af4f55b95579b44b7a01ba?
解析:
1.最简单的方法就是遍历一遍,但是遍历一遍的时间复杂度为O(N),不符合题目的要求。
2.题目中说了是一个非降序数组(可能升序,可能是所有元素相同的数组),将它最开始的几个元素般到末尾,变成一个旋转数组,比如元数组是[1,2,3,4,5],变成了[4,5,1,2,3];从这个例子中,我们可以发现两个规律:
- 最大值的后一位就是最小值。
- 最左边的元素大小一定大于等于最右边的元素大小。
3.设指向最左边的元素的指针为 left, 指向最右边的指针为 right,mid为指向中间元素的指针。
法一:把mid指向的值和left值进行比较
1. 如果a[mid]>a[left] 说明在区间[mid,left]中是一个递增的过程,也就是说最大值所在的位置肯定>=mid;根据第一条规律,最小值所在位置是>mid; 所以,left=mid+1;
2.如果a[mid]<a[left] 说明在区间[mid,left]中存在最小值改变了递增,所以,end=mid;
3.如果a[mid]==a[left],那么可以对left进行++的操作,因为mid和left指向的值是相同的,所以就算left指向的是最小值,区间减小后,也是可以取到mid上的最小值。
4.循环结束条件,根据上面的规律一:a[left]>=a[right],这条语句是为了避免两种情况;情况一:如果测试用例一开始给定的就是一个递减的数组,那么可以直接就不进行循环,返回第一个元素的值也就是最小值;情况二:刚缩小完的范围恰好就是一个递增的数组,那么mid指向的值必然是大于left,所以此时在执行 l=mid+1就会把最小值排除在外。在整个循环过程中 l < r。
具体代码实现:
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
if(rotateArray.empty())
return 0;
int l=0;
int r=rotateArray.size()-1;
int mid=0;
while(rotateArray[l]>=rotateArray[r]&&l<r)
{
mid=(l+r)>>1;
if(rotateArray[mid]>rotateArray[l])
l=mid+1;
else if(rotateArray[mid]<rotateArray[l])
r=mid;
else
l++;
}
return rotateArray[l];
}
};
法二: 把mid指向的值和right指向的值进行比较
1.这个方法比起前一个方法更加简单,因为这个方法不需要判断a[left]>=a[right];因为当范围[left,right]刚好是一个递增的数组时,此时的mid是小于right,执行r=mid的指令,最小值不会被排除掉。
具体代码实现:
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
if(rotateArray.empty())
return 0;
int l=0;
int r=rotateArray.size()-1;
int mid=0;
while(l<r)
{
mid=(l+r)>>1;
if(rotateArray[mid]>rotateArray[r])
l=mid+1;
else if(rotateArray[mid]<rotateArray[r])
r=mid;
else
r--;
}
return rotateArray[l];
}
};