题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。给你一个可能存在重复元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一次旋转,该数组的最小值为1。
示例 1:
示例 2:
解题思路
寻找旋转数组的最小元素即为寻找右排序数组的首个元素 nums[x],称 x为旋转点 。
方案1(剑指offer)
算法流程
- 初始化: 声明 index1, index2 双指针分别指向 nums 数组左右两端;
- 循环二分: 设 mid = (index1+index2) / 2 为每次二分的中点( “/” 代表向下取整除法,因此恒有 index1≤mid<index2),可分为以下三种情况:
- 当 nums[mid]>=nums[index1] 时: mid 一定在左排序数组 中,即旋转点 x 一定在[m+1, index2]
闭区间内,因此执行 index1=mid; - 当 nums[mid]<=nums[index2] 时: mid一定在右排序数组 中,即旋转点 x 一定在[index1,m] 闭区间内,因此执行 index2=mid;
- 当nums[mid]=nums[index2] 时: 无法判断 m 在哪个排序数组中,即无法判断旋转点 x 在[index1,m] 还是 [m+1, index2] 区间中。 解决方案:执行顺序查找,查找[index1,index2]的最小值。
终止条件:当numbers[index1] < numbers[index2]或者index2-index==1时跳出循环。
返回值:返回nums[mid] 即可。
代码
class Solution {
public int minArray(int[] numbers) {
int n = numbers.length;
if(n <= 0 || numbers == null){
return 0;
}
int index1 = 0;
int index2 = n - 1;
int mid = index1;
while(numbers[index1] >= numbers[index2]){
if(index2 - index1 == 1){
mid = index2;
break;
}
mid = (index1 + index2)/2;
//如果下标为index1、index2和mid指向的三个数字相等,则只能顺序查找
if(numbers[index1] == numbers[mid] && numbers[mid] == numbers[index2]){
return MinInOrder(numbers, index1, index2);
}
if(numbers[mid] >= numbers[index1]){
index1 = mid;
}
else if(numbers[mid] <= numbers[index2]){
index2 = mid;
}
}
return numbers[mid];
}
//顺序查找
public int MinInOrder(int[] numbers, int index1, int index2){
int result = numbers[index1];
for(int i = index1 + 1; i <= index2; i++){
if(numbers[i] < result){
result = numbers[i];
}
}
return result;
}
}
方案2(leetcode)
算法流程
- 初始化: 声明 i, j 双指针分别指向 nums 数组左右两端; 循环二分: 设 m=(i+j)/2 为每次二分的中点( "/"代表向下取整除法,因此恒有 i≤m<j ),可分为以下三种情况:
- 当 nums[m]>nums[j] 时: m 一定在 左排序数组中,即旋转点 x 一定在 [m+1,j] 闭区间内,因此执行i=m+1;
- 当 nums[m]<nums[j] 时: m 一定在 右排序数组中,即旋转点 x 一定在[i,m] 闭区间内,因此执行 j = m;
- 当 nums[m]=nums[j] 时: 无法判断 m在哪个排序数组中,即无法判断旋转点 x 在 [i,m] 还是[m+1,j] 区间中。解决方案: 执行 j=j−1 缩小判断范围。
- 返回值:当 i=j 时跳出二分循环,并返回旋转点的值 nums[i] 即可。
代码
class Solution {
public int minArray(int[] numbers) {
int i = 0, j = numbers.length - 1;
while (i < j) {
int m = (i + j) / 2;
if (numbers[m] > numbers[j]) i = m + 1;
else if (numbers[m] < numbers[j]) j = m;
else j--;
}
return numbers[i];
}
}
复杂度分析
- 时间复杂度:平均时间复杂度为 O(logn),其中 n 是数组 numbers 的长度。如果数组是随机生成的,那么数组中包含相同元素的概率很低,在二分查找的过程中,大部分情况都会忽略一半的区间。而在最坏情况下,如果数组中的元素完全相同,那么while 循环就需要执行 n 次,每次忽略区间的右端点,时间复杂度为 O(n)。
- 空间复杂度:O(1)。