1、题目描述
有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。
数据范围:1≤n≤10000,数组中任意元素的值: 0≤val≤10000
要求:空间复杂度:O(1) ,时间复杂度:O(logn)
2、思路分析
如果直接暴力循环求解的话,是行不通的,那样的时间复杂度是O(n)。由原数组是非降序的,所以容易得知旋转后的数组是由两个非降序的子数组组成,左边的子数组的所有元素大于等于右边的子数组的元素,并且两个子数组的分界点就是数组的最小值。所以可以定义一个left=0,right=numsLen-1,当mid的值大于等于left的值时,说明最小值在mid的右边,此时把left放到mid的位置,缩小了范围,并且此时mid任在左边的子数组中。同样,当mid的值小于等于right的值时,说明最小值在mid的左边,此时把right放到mid的位置,缩小了范围,并且此时mid任在右边的子数组中。当left到左子数组的最后一个元素,right到右子数组的第一个元素时,即right-left==1时可以结束循环,此时right的值就是最小值。
此时要特别注意,题目中说的是将“若干个元素搬到数组的末尾”,所以可以是将0个元素搬到数组末尾,即不动数组此时直接将数组第一个值返回即可
left和right的图示
此时可以写出代码:
int minNumberInRotateArray(int* nums, int numsLen ) {
int left = 0,right = numsLen-1,mid = 0;
//如果说第一个元素小于最后一个元素,说明此时是将数组最开始的0个元素搬到数组末尾
if(nums[left]<nums[right]) return nums[left];
while(nums[left]>=nums[right])
{
if(right-left==1)
break;
mid=(left+right)/2;
if(nums[mid]>=nums[left]) left=mid;
else if(nums[mid]<=nums[right]) right=mid;
}
return nums[right];
}
3、代码的修正
此时是根据前面的思路写出的代码,但它真的正确吗?
当数组是{0,1,1,1,1}的一个旋转数组{1,0,1,1,1}时根据上面的代码,可以得出的结果是1,很明显不正确。
所以当left和right指向的数组元素的值相同时,需要遍历所有元素来查找,
于是得到最终代码:
int MinInOrder(int* nums,int left,int right)
{
int result = nums[left];
for(int i = left+1;i<=right;i++)
{
if(result>nums[i]) result = nums[i];
}
return result;
}
int minNumberInRotateArray(int* nums, int numsLen ) {
int left = 0,right = numsLen-1,mid = 0;
//如果说第一个元素小于最后一个元素,说明此时是将数组最开始的0个元素搬到数组末尾
//所以直接返回第一个即可,注意此时不能有等于,如1,0,1,1,1
if(nums[left]<nums[right]) return nums[left];
while(nums[left]>=nums[right])
{
if(right-left==1)
break;
mid=(left+right)/2;
//若left,right,mid三者所指向的数相同,则只能顺序查找
if(nums[left]==nums[mid]&&nums[mid]==nums[right])
return MinInOrder(nums,left,right);
if(nums[mid]>=nums[left]) left=mid;
else if(nums[mid]<=nums[right]) right=mid;
}
return nums[right];
}