一、题目描述
有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。
二、示例
示例一:
输入: [3,4,5,1,2]
返回值: 1
示例二:
输入: [3,100,200,3]
返回值: 3
三、主要思路
1.暴力解法
这道题也可以用暴力解法来做,无论数组怎么旋转,本质还是让我们找最小值,我们可以遍历整个数组找到最小值,暴力解法非常简单,但时间复杂度很高。
2.前后指针法
其实这个方法也不是什么高大上的方法,只是在暴力解法的基础上做了一些小小的优化。
我们可以定义两个指针cur和prev,cur指针在prev指针前面,每次循环,两个指针都向前走一步,直到cur的值比prev的值小为止,此时cur的值就是最小值。
但其实这种方法本质上也是遍历整个数组查找最小值,比上面的暴力解法好的地方只是有可能早一点退出循环,当遇到最坏情况的时候,时间复杂度和暴力解法是一样的。
3.二分比较法
查找的本质就是排除。我们上面介绍的方法都是一个一个在排除,效率非常低。我们可以借助二分查找的思想,一次排除一半的数据。
首先我们观察这道题给的数据,它们原先是非递减的有序的数据,经过旋转之后变成无序的数据,但无论它怎么旋转,最后的数据都可以分成前后两部分,并且这两部分内部都是非递减有序的。
并且我们还可以发现,旋转以后的数据被分成两组,前面一组的数据都是大于后面一组数据的。所以无论怎么旋转,最小值一定在后面一组数据中,并且是后面一组数据的第一个。换句话说,最小值在前面一组数据里最后一个数据的下一个位置。
所以我们可以定义left指针指向旋转后数组的第一个元素,定义right指针指向旋转后数组的最后一个元素。计算中间位置的坐标为mid,让mid的值与left的值进行比较:
- 如果mid的值比left的值大或者相等,说明mid的值属于前面一组数据,最小值在mid的右侧,此时我们就可以让left指向mid所在的位置,right的指向位置不变,继续计算新的mid值。
- 如果mid的值比left的值小,说明mid的值属于后面一组数据,最小值在mid的左侧,此时我们就可以让right指向mid所在的位置,lefi指向的位置不变,继续计算新的mid值。
直到left和right相邻时,right指向的值就是数组的最小值。
但是这道题有一个细节需要注意,由于题目说的是非递减数组,也就是说有可能会存在多个相同的值。有一个极端条件就是:如果left、right和mid指向的值都相等,我们是没办法判断mid的值是属于前面一组的数据还是属于后面一组的数据,此时我们只能针对这种情况从left到right进行遍历查找最小值。
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
int left = 0;
int right = rotateArray.size() - 1;
int mid = 0;
while (rotateArray[left] >= rotateArray[right]) {
// 停止条件
if (right - left == 1) {
mid = right;
break;
}
mid = (left + right) / 2;
// 处理left、right、mid三者相等的特殊情况
if (rotateArray[left] == rotateArray[right] &&
rotateArray[mid] == rotateArray[left])
{
int minRes = rotateArray[left];
for(int i = left + 1; i < right; i++)
{
if(rotateArray[i] < minRes)
{
minRes = rotateArray[i];
}
}
return minRes;
}
// 正常情况
if(rotateArray[mid] >= rotateArray[left])
{
left = mid;
}
else
{
right = mid;
}
}
return rotateArray[mid];
}
};