1 题目
寻找旋转排序数组中的最小值(Find Minimum in Rotated Sorted Array)
lintcode:题号——159,难度——medium
2 描述
假设一个按升序排好序的数组在其某一未知点发生了旋转(比如0 1 2 4 5 6 7 可能变成4 5 6 7 0 1 2)。你需要找到其中最小的元素。(可以假设数组中不存在重复元素。)
名词:
RSA:旋转排序数组,即Rotated Sorted Array
样例1:
输入:[4, 5, 6, 7, 0, 1, 2]
输出:0
解释:数组中的最小值为0
样例2:
输入:[2,1]
输出:1
解释:数组中的最小值为1
3 思路
如果不考虑耗时,通过从头遍历的方式的时间复杂度为O(n)。考虑优化,RSA是正常的排序数组通过旋转得到的,以升序数组为例,旋转后的数组可以看作前后两段升序数组。前半段的所有值都大于后半段的所有值,需要寻找最小值,即后半段的首个元素,考虑以折半查找的方式,每次缩小一半的目标区间。
- 找到中点元素;
- 比较中点元素与末尾元素,根据情况抛掉最小值不可能存在的区间;
- 重复直到目标区间足够小,找到最小值。
在第二步中,选择数组的末尾元素为参照对象进行比较,如果中点元素大于末尾元素,表明中点元素在前升序区间内,因为最小值不可能存在于前升序区间,所以抛掉前半段,折半目标区间;如果中点元素小于末尾元素,表明中点元素在后升序区间内,因为升序的原因,向右的所有元素都不会是最小值,所以可以抛掉后半段,同样能够折半目标区间。
为什么不用首位元素做为参照对象?
步骤2中以末尾元素为参照对象,如果使用首位元素为参照对象,表面上同样能够区分中点元素的位置并折半目标区间,但是考虑标准的升序数组(可以看成未旋转,或者旋转了整圈又回到远点的RSA),以末尾元素为参照,末尾元素始终大于中点元素,抛掉的一直是后半段,直到找到最小值;以首位元素为参照,首位元素始终小于中点元素,抛掉的是前半段,而最小值也被抛掉了,导致结果不正确。
技巧
选择参照对象的时候,永远使用与目标值(被寻找的最大、最小值)在同一升序、降序区间的端点。
即在升序时寻找最小值,由于最小值在后区间,我们选择末尾元素为参照对象;
在升序是寻找最大值,由于最大值在前区间,我们选择首位元素为参照对象;
在降序时寻找最小值,由于最小值在前区间,我们选择首位元素为参照对象;
在降序是寻找最大值,由于最大值在后区间,我们选择末尾元素为参照对象。
3.1 图解
3.2 时间复杂度
算法的时间复杂度为O(log n)
3.3 空间复杂度
算法的空间复杂度为O(1)
4 源码
注意事项:
返回的是最小值,不是序号。
C++版本:
/**
* @param nums: 旋转排序数组(RSA)
* @return: 数组中的最小值
*/
int findMin(vector<int> &nums) {
// write your code here
if (nums.empty())
{
return -1;
}
int start = 0;
int end = nums.size() - 1;
int mid = 0;
while (start + 1 < end)
{
mid = start + (end - start) / 2;
if (nums.at(mid) > nums.at(end))
{
start = mid;
}
if (nums.at(mid) < nums.at(end))
{
end = mid;
}
}
if (nums.at(start) < nums.at(end))
{
return nums.at(start);
}
else
{
return nums.at(end);
}
}