题目
标题和出处
标题:寻找旋转排序数组中的最小值 II
难度
6 级
题目描述
要求
已知一个长度为 n \texttt{n} n 的数组预先按照升序排序,经过 1 \texttt{1} 1 到 n \texttt{n} n 次旋转。例如,原数组 nums = [0,1,4,4,5,6,7] \texttt{nums = [0,1,4,4,5,6,7]} nums = [0,1,4,4,5,6,7] 在变化后可能得到:
- 若旋转 4 \texttt{4} 4 次,则可以得到 [4,5,6,7,0,1,4] \texttt{[4,5,6,7,0,1,4]} [4,5,6,7,0,1,4]。
- 若旋转 7 \texttt{7} 7 次,则可以得到 [0,1,4,4,5,6,7] \texttt{[0,1,4,4,5,6,7]} [0,1,4,4,5,6,7]。
注意,数组 [a[0], a[1], a[2], ..., a[n-1]] \texttt{[a[0], a[1], a[2], ..., a[n-1]]} [a[0], a[1], a[2], ..., a[n-1]] 旋转一次的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] \texttt{[a[n-1], a[0], a[1], a[2], ..., a[n-2]]} [a[n-1], a[0], a[1], a[2], ..., a[n-2]]。
给定一个可能存在重复元素的旋转后的排序数组 nums \texttt{nums} nums,返回数组中的最小元素。
要求尽可能减少总操作次数。
示例
示例 1:
输入:
nums
=
[1,3,5]
\texttt{nums = [1,3,5]}
nums = [1,3,5]
输出:
1
\texttt{1}
1
示例 2:
输入:
nums
=
[2,2,2,0,1]
\texttt{nums = [2,2,2,0,1]}
nums = [2,2,2,0,1]
输出:
0
\texttt{0}
0
数据范围
- n = nums.length \texttt{n} = \texttt{nums.length} n=nums.length
- 1 ≤ n ≤ 5000 \texttt{1} \le \texttt{n} \le \texttt{5000} 1≤n≤5000
- -5000 ≤ nums[i] ≤ 5000 \texttt{-5000} \le \texttt{nums[i]} \le \texttt{5000} -5000≤nums[i]≤5000
- nums \texttt{nums} nums 是一个升序排序的数组进行了 1 \texttt{1} 1 至 n \texttt{n} n 次旋转后的数组
进阶
这道题和「寻找旋转排序数组中的最小值」相似,但是 nums \texttt{nums} nums 可能包含重复元素。这会影响到时间复杂度吗?会如何影响,为什么?
解法
思路和算法
这道题是「寻找旋转排序数组中的最小值」的进阶,给定的数组 nums \textit{nums} nums 中可能包含重复元素。这道题也可以使用二分查找的思想,但是由于数组中可能存在重复元素,因此无法保证达到二分查找的时间复杂度。
为了寻找旋转排序数组中的最小值,需要得到旋转排序数组中的最小值所在下标。
对于长度为 n n n 的升序数组,如果旋转次数等于 n n n 则与旋转前的数组相同,此时最小值位于下标 0 0 0。如果旋转次数小于 n n n 则旋转 i i i 次之后最小值位于下标 i i i。当最小值位于下标 i i i 且 0 < i < n − 1 0 < i < n - 1 0<i<n−1 时,下标 i i i 左侧的任意整数一定大于等于下标 i i i 右侧的任意整数。
用 low \textit{low} low 和 high \textit{high} high 分别表示二分查找的下标范围的下界和上界,初始时 low \textit{low} low 和 high \textit{high} high 分别为数组的最小下标和最大下标。每次查找时, [ low , high ] [\textit{low}, \textit{high}] [low,high] 可能是一个有序子数组或者两个有序子数组。
如果 nums [ low ] < nums [ high ] \textit{nums}[\textit{low}] < \textit{nums}[\textit{high}] nums[low]<nums[high],则 [ low , high ] [\textit{low}, \textit{high}] [low,high] 一定是一个有序子数组,下标 low \textit{low} low 处的整数就是最小值。
如果 nums [ low ] ≥ nums [ high ] \textit{nums}[\textit{low}] \ge \textit{nums}[\textit{high}] nums[low]≥nums[high],则 [ low , high ] [\textit{low}, \textit{high}] [low,high] 可能是一个有序子数组或者两个有序子数组,其中只有当 nums [ low ] = nums [ high ] \textit{nums}[\textit{low}] = \textit{nums}[\textit{high}] nums[low]=nums[high] 时 [ low , high ] [\textit{low}, \textit{high}] [low,high] 才可能是一个有序子数组,此时需要在范围 [ low , high ] [\textit{low}, \textit{high}] [low,high] 中寻找最小值所在下标。取 mid \textit{mid} mid 为 low \textit{low} low 和 high \textit{high} high 的平均数向下取整,得到下标 mid \textit{mid} mid 处的数,判断该数位于哪一个有序子数组中,调整查找的下标范围。
-
如果 nums [ low ] = nums [ mid ] = nums [ high ] \textit{nums}[\textit{low}] = \textit{nums}[\textit{mid}] = \textit{nums}[\textit{high}] nums[low]=nums[mid]=nums[high],则无法确定两个有序子数组的分界位置(即第二个有序子数组的开始下标)是在 mid \textit{mid} mid 处、 mid \textit{mid} mid 的左边还是 mid \textit{mid} mid 的右边,此时无法将查找的下标范围缩小一半,因此在下标范围 [ low + 1 , high − 1 ] [\textit{low} + 1, \textit{high} - 1] [low+1,high−1] 中继续查找。
-
如果 nums [ mid ] < nums [ low ] \textit{nums}[\textit{mid}] < \textit{nums}[\textit{low}] nums[mid]<nums[low],则下标 mid \textit{mid} mid 位于以下标 high \textit{high} high 结束的有序子数组中,最小值位于下标 mid \textit{mid} mid 或其左边,因此在下标范围 [ low , mid ] [\textit{low}, \textit{mid}] [low,mid] 中继续查找。
-
如果 nums [ mid ] ≥ nums [ low ] \textit{nums}[\textit{mid}] \ge \textit{nums}[\textit{low}] nums[mid]≥nums[low],则下标 mid \textit{mid} mid 位于以下标 low \textit{low} low 开始的有序子数组中,最小值位于下标 mid \textit{mid} mid 右边,因此在下标范围 [ mid + 1 , high ] [\textit{mid} + 1, \textit{high}] [mid+1,high] 中继续查找。
二分查找的条件是 low < high \textit{low} < \textit{high} low<high 且 nums [ low ] ≥ nums [ high ] \textit{nums}[\textit{low}] \ge \textit{nums}[\textit{high}] nums[low]≥nums[high],当该条件不成立时,二分查找结束,此时的 low \textit{low} low 即为最小值所在下标。理由如下。
-
如果 low < high \textit{low} < \textit{high} low<high 不成立,则 low = high \textit{low} = \textit{high} low=high,此时二分查找的范围仅限于一个下标。由于被排除的下标都不可能是最小值所在下标,因此 low \textit{low} low 为最小值所在下标。
-
如果 nums [ low ] ≥ nums [ high ] \textit{nums}[\textit{low}] \ge \textit{nums}[\textit{high}] nums[low]≥nums[high] 不成立,则 nums [ low ] < nums [ high ] \textit{nums}[\textit{low}] < \textit{nums}[\textit{high}] nums[low]<nums[high],范围 [ low , high ] [\textit{low}, \textit{high}] [low,high] 中的最小元素位于下标 low \textit{low} low。由于被排除的下标都不可能是最小值所在下标,因此 low \textit{low} low 为最小值所在下标。
代码
class Solution {
public int findMin(int[] nums) {
int low = 0, high = nums.length - 1;
while (low < high && nums[low] >= nums[high]) {
int mid = low + (high - low) / 2;
if (nums[low] == nums[mid] && nums[high] == nums[mid]) {
low++;
high--;
} else if (nums[mid] < nums[low]) {
high = mid;
} else {
low = mid + 1;
}
}
return nums[low];
}
}
复杂度分析
-
时间复杂度:平均情况是 O ( log n ) O(\log n) O(logn),最差情况是 O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。平均情况下,二分查找的次数是 O ( log n ) O(\log n) O(logn),每次查找的时间是 O ( 1 ) O(1) O(1),时间复杂度是 O ( log n ) O(\log n) O(logn)。最差情况下,数组 nums \textit{nums} nums 中的所有元素都相等,此时需要遍历整个数组。
-
空间复杂度: O ( 1 ) O(1) O(1)。