【面试题】旋转数组的最小数字(二分查找法 + 详细题解思路分析)

题目描述

在这里插入图片描述
这道题最直观的解法并不难,从头到尾遍历数组一次,就能找到最小的元素,时间复杂度为 O(n)。但是这个思路没有利用到输⼊的旋转数组的特性,在有序数组中用二分查找法可以实现 O(logn) 的查找。我们注意到,旋转之后的数组实际上可以分为两个有序的子数组,而且前面的子数组的元素都大于等于后面子数组中的元素,此外,我们还注意到这个最小的元素正好是两个子数组的分界线。因为,我们可以尝试用二分查找的思路来寻找这个分界点。

  1. 同二分查找算法的思路,定义两个指针 index1 和 index 2,初始分别指向数组的第一个元素和最后一个元素。
  2. 计算得到数组中间元素的下标 indexMid = (index1 + index2)/2,判断该中间元素与第一个元素和最后一个元素的大小关系。
  3. 如果该中间元素属于前面的递增子数组,那么它应该大于等于 index1 指向的元素。此时数组中最小的元素应该位于该中间元素的后面。我们可以把第⼀个指针 index1 指向该中间元素,缩小寻找的范围,移动之后的第⼀个指针仍然位于前面的递增子数组中。
  4. 反之,如果该中间元素属于后面的递增子数组,那么它应该小于等于 index2 指向的元素。此时数组中最小的元素应该位于该中间元素的前面。我们可以把第二个指针 index2 指向该中间元素,缩小寻找的范围,移动之后的第二个指针仍然位于后面的递增子数组中。
  5. 这样一来,不管是移动 index1 还是 index2,查找范围都会缩小一半,然后我们再用更新后的两个指针,重复上述过程。
  6. 最终,第一个指针 index1 将指向前面递增子数组的最后一个元素,第二个指针 index2 会指向后面递增子数组的第一个元素,也即它们会指向两个相邻的元素。此时,第二个指针指向的正好就是分界点,即数组中最小的元素。这是循环结束的条件。
  • 特例一:如果是把递增数组的前面 0个元素搬到末尾,即数组本身, 这仍然是数组的⼀个旋转,此时,数组中的第一个元素即为最小元素,直接返回即可。为了处理这种情况,我们可以把 indexMid 初始化为 index1,一旦发现第一次循环就有数组第一个数字 numbers[index1] < 最后一个数字 numbers[index2],说明该数组本身已是递增的,直接返回第一个元素 numbers[indexMid] 也就是 numbers[index1]。
  • 特例二:当两个指针指向的数字及它们中间的数字三者相同的时候,我们无法判断中间元素是位于前面的子数组中还是后面的子数组中,也就无法移动两个指针来缩小查找的范围。此时,只能退化为顺序查找的方法。
    在这里插入图片描述

Python

class Solution(object):
    def minArray(self, numbers):
        """
        :type numbers: List[int]
        :rtype: int
        """
        if len(numbers) == 0: return None

        # 顺序查找函数
        def findMinInOrder(left, right):
            res = numbers[left]
            for i in range(left, right+1):
                if numbers[i] < res:
                    res = numbers[i]
            return res

        index1 = 0
        index2 = len(numbers) - 1
        indexMid = index1 # 初始化为 index1的原因是考虑到数组已是有序的情况
        while numbers[index1] >= numbers[index2]: # ⼀旦发现数组中第⼀个数字⼩于最后⼀个数字,表明该数组已是排好序的,直接返回第一个元素
            if index2 - index1 == 1: # 循环结束条件,两个指针指向两个相邻的元素,此时第二个指针指向的即为分界点。
                indexMid = index2
                break
            indexMid = (index1 + index2)//2 # 向下取整

            # 当下标为index1,index2,indexMid指向的三个数字相等,则无法判断中间的数字是位于前⾯的⼦数组中还是后⾯的⼦数组中,
            # 也就无法移动指针进一步缩小查找范围,不得不退化为顺序查找
            if numbers[indexMid] == numbers[index1] and numbers[indexMid] == numbers[index2]:
                return findMinInOrder(index1, index2)

            if numbers[indexMid] >= numbers[index1]: # indexMid 处于左边的递增子数组中,那么分界点一定位于它后面,更新左边界缩小查找范围
                index1 = indexMid
            if numbers[indexMid] <= numbers[index2]: # indexMid 处于右边的递增子数组中,那么分界点一定位于它前面,更新右边界缩小查找范围
                index2 = indexMid
            
        return numbers[indexMid]

Java

class Solution {
    public int minArray(int[] numbers) {
        int left = 0;
        int right = numbers.length - 1;
        int mid = left;
        while (numbers[left] >= numbers[right]){
            if (right - left == 1){ // 循环结束条件
                mid = right; // 此时第二个指针指向的元素即为最小元素
                break;
            }
            mid = (left+right)/2;
            if (numbers[left] == numbers[mid] && numbers[mid] == numbers[right]){
                return findInOrder(numbers, left, right); // 三者相同时,采用顺序查找
            }
            if (numbers[mid] >= numbers[left]) // 中间元素属于前面子数组,最小元素位于该中间元素的后面
                left = mid;
            if (numbers[mid] <= numbers[right]) // 中间元素属于后面子数组,最小元素位于该中间元素的前面
                right = mid;
        } 
        return numbers[mid];
    }

    public int findInOrder(int[] numbers, int left, int right){
        int res = numbers[left];
        for (int i=0; i<=right; i++){
            if (numbers[i] < res)
                res = numbers[i];
        }
        return res;
    }
}

参考

https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/solution/mian-shi-ti-11-xuan-zhuan-shu-zu-de-zui-xiao-shu-3/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值