给你一个可能存在 重复 元素值的数组 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
问题转化:
是由两个有序的数组,组合在一起的。所以可以转化为:对两个数组的分界点的寻找。
方法1:暴力解法
算法分析:
- 从下标为0的元素开始遍历。
- 每次进行比较,如果当前元素比相邻下一个元素大,则对应的下一元素即为最小值。
- 如果查到最后元素都没有出现这两种情况,则下标为0的元素为最小元素。(当所有元素都相等时)
int minArray(int* numbers, int numbersSize){ int i, min = numbers[0]; for(i = 1; i < numbersSize; i++){ if(numbers[i] < min) min = numbers[i]; } return min; }
复杂度分析:
- 时间复杂度维O(N)
- 空间复杂度为O(1)
方法2:二分法
一般二分查找过程:
- 找到中间的关键字;
- 比较查找的关键字与中间关键字的大小关系;
- 如果相等就相当于已经找到;
- 如果查找的关键字小于中间的关键字,则在前半部分进行同样的过程(从小到大存储)
- 如果查找的关键字大于中间的关键字,则在后半部分进行同样的过程(从小到大存储)
一般二分查找的要求:
- 顺序存储
- 元素有序
原因:
- 通过下标即可得到关键字
- 任取一个关键字的值即可确定所找关键字是在他前面还是后面。
假设没有重复数字:
- 如果numbers[mid]>numbers[right]
- 则前半部分一定是有序的
- 最小值(分界点)在mid后面
- 如果numbers[mid]<numbers[right]
- 则后半部分一定有序
- 最小值(分界点)在mid前面
假设有重复数字:
- 则不满足任取一个关键字的值就可确定所招关键字是在他前面还是后面。
- 所以不可以用二分查找。
解决方案:
- 从右向左暴力的遍历查找与二分查找相结合。
-
int minArray(int* numbers, int numbersSize){ int left=0; int right=numbersSize-1; while(left<right){ int mid=left+(right-left)/2;//左侧加上两个元素的差值 if(numbers[mid]>numbers[right]){ left=mid+1; } else if(numbers[mid]<numbers[right]){ right=mid; } else if(numbers[mid]==numbers[right]{ right--; } } return numbers[left]; }
三个问题:
-
为什么right--不会对结果产生影响?
-
为什么right=mid而left=mid-1?
-
时间/空间复杂度如何计算
1.为什么right--不会对结果产生影响?
- 产生影响的条件:删除的元素为唯一最小元素。
- 执行条件:numbers[right]=numbers[mid]
- 矛盾
2.为什么right=mid而left=mid+1?
- 一般二分查找:left=mid+1,right=mid-1.
- 原因:确定mid所指元素并非查找元素。
此题的二分查找:
- numbers[mid]>numbers[right]时,mid所指的一定不是最小值,
- 因为一定比numbers[right]大所以left=mid+1
- numbers[mid]<numbers[right]时,mid所指可能是最小值,所以right=mid。
时间复杂度分析:
- 一般情况:与二分相同为O(logN)
- 特殊情况:始终执行right--,复杂度为O(N).
- 空间复杂度为O(1).