题目信息
-
时间: 2019-07-23
-
题目链接:Leetcode
-
tag: 二分查找
-
难易程度:简单
-
题目描述:
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [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为 旋转点 。
排序数组的查找问题首先考虑使用 二分法 解决,其可将遍历法的 线性级别 时间复杂度降低至 对数级别 。
- 循环二分:
- 当 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 缩小判断范围 。
展开分析 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>xj>x : 易得执行 j=j−1j=j−1 后旋转点 xx 仍在 [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[m]
和numbers[i]
比较做代替?不可以。因为做比较的目的是判断 m 在哪个排序数组中。但在 numbers[m] > numbers[i]情况下,无法判断 m 在哪个排序数组中。本质是因为 j 初始值肯定在右排序数组中; i 初始值无法确定在哪个排序数组中。
代码
class Solution {
public int minArray(int[] numbers) {
int l = 0;
int r = numbers.length-1;
while(l < r){
int mid = (l + r)/2;
if(numbers[mid] > numbers[r]){
l = mid + 1;
}else if(numbers[mid] < numbers[r]){
r = mid;
}else{
r--;
}
}
return numbers[l];
}
}
复杂度分析:
- 时间复杂度 O(logN) : 在特例情况下(例如 [1,1,1,1]),会退化到 O(N)。
- 空间复杂度 O(1) : i , j , m指针使用常数大小的额外空间。