0
点赞
收藏
分享

微信扫一扫

【剑指 Offer 11. 旋转数组的最小数字】

Sikj_6590 2022-03-12 阅读 79

题目描述

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。给你一个可能存在重复元素值的数组 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),可分为以下三种情况:
  1. 当 nums[mid]>=nums[index1] 时: mid 一定在左排序数组 中,即旋转点 x 一定在[m+1, index2]
    闭区间内,因此执行 index1=mid;
  2. 当 nums[mid]<=nums[index2] 时: mid一定在右排序数组 中,即旋转点 x 一定在[index1,m] 闭区间内,因此执行 index2=mid;
  3. 当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 ),可分为以下三种情况:
  1. 当 nums[m]>nums[j] 时: m 一定在 左排序数组中,即旋转点 x 一定在 [m+1,j] 闭区间内,因此执行i=m+1;
  2. 当 nums[m]<nums[j] 时: m 一定在 右排序数组中,即旋转点 x 一定在[i,m] 闭区间内,因此执行 j = m;
  3. 当 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)
举报

相关推荐

0 条评论