面试题11.旋转数组的最小数字

题目

把一个数组最开始的若干元素搬到数组的末尾,称之为数组的旋转。 输入一个排序好了的递增数组的旋转,要求输出旋转数组的最小元素。
如输入:[5,7,1,2,3]
输出:1
注意是否包含重复元素

算法思路

为了追求算法高效,减低复杂度,如果单单是找出最小元素,直接遍历一遍就可有得出,时间复杂度为O(N ),而很显然本题需要的不是该方法,有比挨个遍历数组更高效的方法,下面来分析题目,数组将原本已经升序排序好的数组通过分割,分割成为两部分,而左边那部分变为右边部分,如下图:
在这里插入图片描述

通过上图分析,可以使用二分法 ,步骤如下:设置两个指针 l ,r , 分别指向数组nums的两端,循环二分,定义一个mid为每次二分的中点,必有 l <= mid < r,有如下三种情况:

  1. 当nums[mid] > nums[r],例如[5,6,7,1,2],nums[mid] 大于nums[r],说明最小元素必然在mid的右边,此时更新 l 的值,l = mid+1,继续循环二分 ;
  2. 当nums[mid] < nums[r],例如[7,1,2,3,4],nums[mid] < nums[r],说明最小元素必然mid的左边,当然也有可能最小元素就是mid,如[5,7,1,2,3]就是如此,所以,此时更新r的值,r = mid,继续二分;
  3. 当nums[mid] = nums[r]时,说明出现重复数据,不能够判断最小元素的位置,所以此时缩小范围,r自减1,再继续二分查找。

当 l = r 时,退出循环,返回结果,即为nums[l]。

代码

class Solution {
    public int minArray(int[] nums) {
        int l = 0,r = nums.length-1;
        if(nums[l] < nums[r]) return nums[l];
        while(l < r){
            //int mid = ((r - l) >> 1) + l; int mid = (l+r)/2
            int mid = (r + l) >> 1;
            if (nums[r] > nums[mid]) {
                r = mid;
            } else if (nums[r] < nums[mid]) {
                l = mid + 1;
            } else r--;
        }
        return nums[l];
    }
}

时间复杂度:O(logN),空间复杂度:O(1),只引入了常数个变量。

至此题目解答了,但仍然有两个点值得思考:一是为什么是nums[mid] 和 nums[r]比较,而不是nums[mid] 和 nums[l];二是重复元素的处理是r–。下面一一解答。

**问题一:**前面图中已经分析的很清楚,左右两个排序的数组中,最小元素必然在右排序数组中,若和mid和 l 出的值作比较,当nums[mid] > nums[l] 时,无法判断最小值在哪边,也就是无法判断mid在哪个排序数组,本质就是 r 的初始值必然在右排序数组中,而 l 则无法确定。如[4,5,6,1,2],就无法判断。

**问题二:**重复元素的处理。即当nums[mid] = nums[r] ,无法判断mid在左右哪个排序数组中,也就无法判断最小元素的位置,所以,需要缩小搜索范围,r 自减1,可能你会说,既然nums[mid] = nums[r] ,那就是说从mid 处的元素到 r 处的元素都相等,那就错了,如:[3,3,1,3]这种情况就不等。所以需要r–。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值