153. 寻找旋转排序数组中的最小值 + 二分法
题目来源
相似题目
33. 搜索旋转排序数组
153. 寻找旋转排序数组中的最小值
154. 寻找旋转排序数组中的最小值 II
题目描述
题解分析
解法一:二分法-思路一
- 这道题高效的解法就是二分法,利用旋转数组部分有序的性质找到最小值。
- 首先考虑一种情况:数组未旋转或者旋转后回到原来的升序状态,此时只需要判断第一个元素是否大于最后一个元素就可以判断是否发生旋转,返回第一个元素即最小值。
- 在二分查找的过程中可以判断是否到达分界线,找到分界线即可返回最小值。
- 最后根据mid所在的数字和nums[0]的比较来划分下一次查询的范围。
class Solution {
public int findMin(int[] nums) {
int n = nums.length;
int low = 0, high = n-1;
if(nums[0] <= nums[n-1])//这里是小于等于,不是小于,因为数组中可能只有一个数
return nums[0];
while(low <= high){//这里是小于等于,而不是小于
int mid = (low + high) >> 1;
//mid在旋转的分界线上
if(nums[mid] > nums[mid+1])
return nums[mid+1];
if(nums[mid-1] > nums[mid])
return nums[mid];
if(nums[mid] > nums[0])//这里是>没有等于的比较
low = mid+1;
else high = mid-1;
}
return nums[low];
}
}
解法二:二分法-思路二
图文解析
用二分法查找,需要始终将目标值(这里是最小值)套住,并不断收缩左边界或右边界。
左、中、右三个位置的值相比较,有以下几种情况:
-
左值 < 中值, 中值 < 右值 :没有旋转,最小值在最左边,可以收缩右边界
右 中 左
-
左值 > 中值, 中值 < 右值 :有旋转,最小值在左半边,可以收缩右边界
左 右 中
-
左值 < 中值, 中值 > 右值 :有旋转,最小值在右半边,可以收缩左边界
中 左 右
-
左值 > 中值, 中值 > 右值 :单调递减,不可能出现
左 中 右
分析前面三种可能的情况,会发现情况1、2是一类,情况3是另一类。
- 如果中值 < 右值,则最小值在左半边,可以收缩右边界。
- 如果中值 > 右值,则最小值在右半边,可以收缩左边界。
通过比较中值与右值,可以确定最小值的位置范围,从而决定边界收缩的方向。
而情况1与情况3都是左值 < 中值,但是最小值位置范围却不同,这说明,如果只比较左值与中值,不能确定最小值的位置范围。
所以我们需要通过比较中值与右值来确定最小值的位置范围,进而确定边界收缩的方向。
细节问题
- 这里的循环不变式是
left < right
, 并且要保证左闭右开区间里面始终套住最小值。 - 中间位置的计算:
mid = left + (right - left) / 2
,这里整数除法是向下取整的地板除,mid
更靠近left
, 再结合while循环的条件left < right
, 可以知道left <= mid
,mid < right
, 即在while循环内,mid
始终小于right
。 - 因此在while循环内,
nums[mid]
要么大于要么小于nums[right]
,不会等于。 - 再分析一下while循环退出的条件。
- 如果输入数组只有一个数,左右边界位置重合,
left == right
,不会进入while循环,直接输出。 - 如果输入数组多于一个数,循环到最后,会只剩两个数,
nums[left] == nums[mid]
,以及nums[right]
,这里的位置left == mid == right - 1
。 - 如果
nums[left] == nums[mid] > nums[right]
,则左边大、右边小,需要执行left = mid + 1
,使得left == right
,左右边界位置重合,循环结束,nums[left]
与nums[right]
都保存了最小值。 - 如果
nums[left] == nums[mid] < nums[right]
,则左边小、右边大,会执行right = mid
,使得left == right
,左右边界位置重合,循环结束,nums[left]
、nums[mid]
、nums[right]
都保存了最小值。
- 如果输入数组只有一个数,左右边界位置重合,
class Solution {
public int findMin(int[] nums) {
int n = nums.length;
int left = 0, right = n-1;
while(left < right){
int mid = left + (right - left) / 2;
if(nums[mid] > nums[right]){
left = mid + 1;// 此时mid的位置绝对不可能是最小值,所以mid需要加1
}else if(nums[mid] < nums[right]){
right = mid;// 此时mid的位置有可能是最小值,所以mid不需要减1
}
}
return nums[left];
}
}
Either Excellent or Rusty