数据结构习题--寻找旋转排序数组中的最小值
已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]
可以理解旋转几次,每一次就是把最后一个数拿到数组最前面
注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。
给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
方法一:复杂度O(n)
分析
我们知道原来的数组是单调递增的,而经历过旋转之后,它具有的特点是前面一段和后面一段都是单调递增的,但是前面一段的最小值大于后面一段的最大值,而我们需要寻找的最小值,是在两段的分界点
而对于我们取第一个元素为最小值,遍历数组,当有元素比其小,就退出循环,该值就是我们需要寻找的最小值
代码
java
public int findMin(int[] nums) {
int min = nums[0];
for (int i = 1; i < nums.length; i++) {
if (nums[i] < min){
return nums[i];
}
}
return min;
}
方法二:二分查找 复杂度O(logn)
分析
我们知道原来的数组是单调递增的,而经历过旋转之后,它具有的特点是前面一段和后面一段都是单调递增的,但是前面一段的最小值大于后面一段的最大值,而我们需要寻找的最小值,是在两段的分界点
而我们设置一个low在数组最左边,设置high在数组最右边,而mid为其中间,当mid的元素小于high的元素时,我们就不需要管其右边的元素,直接令high到mid的位置,然后重新计算mid,当mid的元素不大于high的元素时,我们令low = mid + 1,
代码
java
public int findMin(int[] nums) {
/**
* high 表示右边的元素
* low 表示左边的元素
*/
int high = nums.length- 1;
int low = 0;
while (low < high){
// 寻找中间的数,进行二分
int mid = (high + low) / 2;
// 当中间的数小于右边的数,更新此位置为右边
if (nums[mid] < nums[high]){
//如果中间值小于最大值,则最大值减小
// 疑问:为什么 high = mid;而不是 high = mid-1;
// 解答:{4,5,1,2,3},如果high=mid-1,则丢失了最小值1
high = mid;
}else {
// 如果中间值大于最大值,最小值变大
// 疑问:为什么 low = mid+1;而不是 low = mid;
// 解答:{4,5,6,1,2,3},nums[mid]=6,low=mid+1,刚好nums[low]=1
// 继续疑问:上边的解释太牵强了,难道没有可能low=mid+1,正好错过了最小值
// 继续解答:不会错过!!! 如果nums[mid]是最小值的话,则其一定小于nums[high],走if,就不会走else了
low = mid + 1;
}
}
// 疑问:为什么while的条件是low<high,而不是low<=high呢
// 解答:low<high,假如最后循环到{*,10,1,*}的这种情况时,nums[low]=10,nums[high]=1,nums[mid]=10,low=mid+1,
// 直接可以跳出循环了,所以low<high,此时low指向的就是最小值的下标;
// 如果low<=high的话,low=high,还会再不必要的循环一次,此时最后一次循环的时候会发生low==high==mid,
// 则nums[mid]==nums[high],则会走一次else语句,则low=mid+1,此时low指向的是最小值的下一个下标,
// 则需要return[low-1]
return nums[low];
}