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


    题目描述

    把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

    解法1

    对于非减数组来说,数组右边的元素一定大于等于数组左边的元素。当对非减数组进行旋转后(把数组最开始的元素搬到末尾),则在遍历过程中可能会出现右边的元素反而小于左边的元素,当第一次出现这种情况时,一定是原非减数组的开头,即整个数组的最小元素。

    实现代码

    public int minNumberInRotateArray(int[] rotateArray)
    {
        if (rotateArray.Length <= 0)
        {
            return 0;
        }
        for (int i = 1; i < rotateArray.Length; i++)
        {
            if (rotateArray[i - 1] > rotateArray[i])
            {
                return rotateArray[i];
            }
        }
        return rotateArray[0];
    }
    

    二分查找

    解法1是顺序遍历数组找到最小值,这样的时间复杂度是O(n),那么有没有什么办法进行优化呢?
    本题要查找的是非减排序数组的旋转数组的最小值。其实是有一定顺序的,对于有序数组的查找,我们自然想到了二分查找。
    先介绍一下二分查找的基本思想:
    首先,假设数组中元素是按升序排列,将数组中间位置元素与要查找的元素比较,如果两者相等,则查找成功;否则利用中间位置索引将数组分成左、右两个子数组,如果中间位置的元素大于要查找的元素,则进一步查找左子数组,否则进一步查找右子数组。重复以上过程,直到找到满足条件的元素,使查找成功,或直到子数组不存在为止,此时查找不成功。
    二分查找的时间复杂度是O(log2n)

    解法2

    以数组arr = {3,4,5,1,2}为例,可以分成两个有序非减数组来看待,如下图所示
    两个非减数组
    显然,数组的最小值就在两个非减数组的交界处,同时由于arr是一个非减数组旋转得到的,所以左边数组的最小值一定大于等于右边数组的最小值。利用二分查找,使左边的指针指向索引0即3,右边的索引指向索引4即2,求得mid = low + (high - low)/2 = 2,比较arr[mid]和arr[high]的值(这里说明为什么不使用arr[mid]和arr[low]进行比较,因为按照上面的算法,mid有可能等于low,再比较arr[mid]和arr[low]没有意义)

    • 如果arr[mid] > arr[high],则说明当前的mid,处于左边的非减数组中,则最小值在mid的右边,则将low指向mid +
      1
    • 如果arr[mid] < arr[high],则说明当前的mid,处于右边的非减数组中,则最小值在mid的左边,则将high指向mid(这里说明为什么high不指向mid - 1,同样因为mid的算法,可能存在mid = low = 0,如果high = mid - 1,则high有可能小于0,为了避免这种情况的判断,所以采用high = mid )

    如果是求一个递增数组的旋转数组的最小值,则上述逻辑已经足够,但本题是求非减数组的旋转数组的最小值,也就是说可能存在两个元素相等的情况。
    比如旋转数组{1,0,1,1,1}和{1,1,1,0,1}都可以看成非减数组{0,1,1,1,1}的旋转数组
    此时对于它们而言arr[mid] = arr[high],这种情况下我们并不知道mid是在最小值的左边还是右边,比如这两个旋转数组,一个是在mid的左边,一个反而在mid的右边。当出现这种情况时我们可以认为数组的有序性丢失了,不能再继续使用二分查找,而只能顺序遍历从low到high找到最小值,即high = high - 1或者low = low + 1。

    实现代码

    public int minNumberInRotateArray(int[] rotateArray)
    {
        if (rotateArray.Length <= 0) {
            return 0;
        }
        int low = 0, high = rotateArray.Length - 1;
        while(high > low) {
            int mid = low + (high - low) / 2;
            if (rotateArray[mid] > rotateArray[high]) {
                low = mid + 1;
            }else if (rotateArray[mid] < rotateArray[high]) {
                high = mid;
            }else {
                high = high - 1;
            }
        }
        return rotateArray[low];
    }
    

    一点想法

    在想到用二分查找优化本题的时候,其实遇到了问题,就是上面有提到的当中间元素等于高位元素时,不知道应该左移还是右移的问题。一度觉得这道题可能用二分查找解不了,后来看到某个大神的代码,才恍然大悟,这种情况下退化成顺序查找就可以。想不到这种方法的原因还是太执着于二分查找的标准形式。一直是在套用算法,而没有想到变通,或融合其他算法。

    更多题目的完整描述,AC代码,以及解题思路请参考这里https://github.com/iwiniwin/Algorithm

  • 相关阅读:
    hibernate4 无法保存 不报错
    win10开启mongodb 开启服务
    nodejs学习笔记
    mariadb Too many connections
    新老ECS数据库速度对比
    数据库自动备份并打成tar.gz包
    字符串和数组----string
    I/O复用(select)——回声服务器端/客户端
    回声UDP服务器端/客户端
    回声TCP服务器端/客户端
  • 原文地址:https://www.cnblogs.com/iwiniwin/p/10793650.html
Copyright © 2020-2023  润新知