题目
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
示例 1:
输入:[3,4,5,1,2]
输出:1
示例 2:输入:[2,2,2,0,1]
输出:0
分析思路
-
如下图所示,寻找旋转数组的最小元素即为寻找 右排序数组 的首个元素 numbers[x] ,称 x 为 旋转点 。
-
排序数组的查找问题首先考虑使用 二分法 解决,其可将遍历法的 线性级别 时间复杂度降低至 对数级别 。
方法:二分查找
算法流程
-
循环二分: 设置 i, j 指针分别指向 numbers 数组左右两端,m = (i + j) // 2为每次二分的中点( “//” 代表向下取整除法,因此恒有 i ≤ m < j),可分为以下三种情况:
- 当numbers[m] > numbers[j] 时: m一在左排序数组中,即旋转点x一定在[m+ 1,j]闭区间内,因此执行i= m+ 1;
- 当numbers[m] < numbers[j] 时: m一定在右排序数组中,即旋转点x一定在[i, m]闭区间内,因此执行j = m;
- 当numbers[m] = numbers[j] 时:无法判断m在哪个排序数组中,即无法判断旋转点x在[i, m]还是[m+ 1,j]间中。解决方案:执行j= j- 1缩小判断范围(分析见以下内容) 。
-
返回值:当i = j时跳出二分循环,并返迥numbers[i]即可。
**思考:**是否可以用numbers[m] 和numbers[i]|比较做代替?
**解析:**不可以。因为做比较的目的是判断m在哪个排序数组中。在numbers [m]>numbers[i] 情况下,无法判断m在哪个排序数组中。本质是因为j初始值肯定在右排序数组中; i 初始值无法确定在哪个排序数组中。
示例:当i=0,j= 4,m=2时,有numbers[m] > numbers[i] , 以下两示例
得出不同结果。
numbers= [1, 2,3,4,5]旋转点x= 0: m在右排序数组(际例只有右排序
数组) ;
numbers= [3, 4,5,1,2]旋转点x= 3: m在左排序数组。展开分析numbers[m] = numbers[j] 情况:
- **无法判定m在左(右)排序数组:**设以下两个旋转点值为 0的示例数组,则当i=0,j= 4时m=2,两际例结果不同。
- 例[1,0,1,1,1]:旋转点x= 1,因此m= 2在右排序数组中。
- 例[1,1,1,0,1]:旋转点x=3,因此m = 2在左排序数组中。
- j= j - 1操作的正确性证明:只需证明晦次执行此操作后,旋转点x仍在[i, j]区间内即可。
- 若m在右排序数组中: numbers[m] == numbers[j],因此数组[m,j] (恒有m < j)间内所有元素值相等,执行j= j- 1只会抛弃-个重复值,因此旋转点x仍在[i, j]区间内。
- **若m在左排序数组中:**于|左排序数组任一元素>=右排序数组任一元素|,因此可推出旋转元素值numbers[x] <= numbers[j] == numbers[m],则有:
- 若numbers[x] < numbers[j] :即j方仍有值更小的元素,执行j= j- 1后旋转点x 仍在[i, j]区间内。
- 若numbers[x] = numbers[j] :分为以下两种情况。
- 当j> x:易得执行j= j- 1后旋转点x仍在[i,j]区间内。
- 当j= x:特殊情况,即执行j=j- 1后旋转点x可能不在[i, j]区间内。例如
[1,1,1,2,3,1],当i=0,m=2,j= 5时执行j= j- 1后虽然秩了旋转索引x= 5,但最终返回值仍正确(最终返回numbers[0] 等于旋转点值numbers[5]),这是因为:之后的二分循环一直在执行j = m,区间[i, m]内的元素值一定都等于旋转点值numbers[x] ( :区间内元素值既要满足≥也要满足< numbers[x] ),因此仍可保证正确的返回值。
- 总结:仿河以保证返回值numbers[i]| 等于旋转点值numbers[x] ;但在少数特例下i不是旋转
点x。本题目职要求返回“旋转点的值”,因此本方法可行。
- **无法判定m在左(右)排序数组:**设以下两个旋转点值为 0的示例数组,则当i=0,j= 4时m=2,两际例结果不同。
public int minArray(int[] numbers) {
int left = 0, right = numbers.length - 1;
while(left < right) {
int mid = left + (right - left) / 2;
if(numbers[mid] < numbers[right]) {
right = mid;
}else if(numbers[mid] > numbers[right]) {
left = mid + 1;
}else {
right--;
}
}
return numbers[left];
}
python
class Solution:
def minArray(self, numbers: [int]) -> int:
i, j = 0, len(numbers) - 1
while i < j:
m = (i + j) // 2
if numbers[m] > numbers[j]: i = m + 1
elif numbers[m] < numbers[j]: j = m
else: j -= 1
return numbers[i]