题目
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一次旋转,该数组的最小值为 1。
注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。
示例 1:
输入:numbers = [3,4,5,1,2]
输出:1
示例 2:
输入:numbers = [2,2,2,0,1]
输出:0
题解
递增排序,左边小于右边,使用二分查找,中间小于右边!
题目中给出的是半有序数组,传统二分只能用在有序数组中,但事实上,只要是可以减治的问题,仍然可以使用二分思想。
思路
数组中最特殊的位置是左边位置 left 和右边位置 right,将它们与中间位置 mid 的值进行比较,进而判断最小数字出现在哪里。
用左边位置 left 和中间位置 mid 的值进行比较是否可以?
举例:[3, 4, 5, 1, 2] 与 [1, 2, 3, 4, 5] ,此时,中间位置的值都比左边大,但最小值一个在后面,一个在前面,因此这种做法不能有效地减治。
用右边位置 right 和中间位置 mid 的值进行比较是否可以?
举例:[1, 2, 3, 4, 5]、[3, 4, 5, 1, 2]、[2, 3, 4, 5 ,1],用右边位置和中间位置的元素比较,可以进一步缩小搜索的范围。
当nums[mid] == nums[right] 的时候,不能确定最小数字一定在哪一边,但是可以确定的是,把 right 舍弃掉,并不影响结果。
代码
class Solution {
public int minArray(int[] numbers) {
//设置left, right指针分别指向numbers数组左右两端
//left指向当前区间的最左边位置,所以初始化为0
int left = 0 ;
//right指向当前区间的最右边位置,所以初始化为nums.length - 1
int right = numbers.length - 1;
//循环进行二分查找,直到左端点位置超过了右端点
//或者在循环过程中找到了起始位置
while(left<right){
//mid 为中点(这里向下取整,比如( 2 +7 )/ 2 = 4 )
int mid=left+(right-left)/2;
//当mid点所在元素大于数组末端的元素时,因原数组是递增有序的,此时出现异常,大的数在前面
//所以旋转点在[ mid + 1, end ]区间里面
if(numbers[mid]>numbers[right]){
//所以旋转点在〔mid + 1,end ]区间里面,更新 left 的位置为mid + 1
left=mid+1;
//当mid 点所在元素小于数组末端的元素时,由于原来的数组是递增有序的
//所以旋转点在[ left, mid ]区间里面
}else if (numbers[mid]<numbers[right]){
//旋转点在[ left,mid ] 区间里面,更新right 的位置为mid
right=mid;
//此时出现了numbers[mid]=numbers[end]的情况,无法判断
//[ start , mid ]为有序数组区间
//还是[ mid , end ]为有序数组区间
//比如:[1,0,1,1,1]和[1,1,1,0,1]
}else{
//所以这里采取遍历的方式
//return findMin(numbers,left,right);
right--;
}
}
return numbers[left];
}
//从头到尾遍历numbers ,获取到最小值
public int findMin( int[] numbers,int left,int right){
//默认为数组的第一个元素为最小值
int result = numbers [left];
//从头到尾遍历numbers
for(int i = left ; i <= right ; i++){
//当发现此时遍历的元素值小于result
if ( numbers[i] < result) {
//那么更新result
result = numbers [i];
}
}
//返回numbers中的最小值
return result;
}
}