题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。给你一个可能存在重复元素值的数组 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)。










