zoukankan      html  css  js  c++  java
  • 【剑指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

  • 相关阅读:
    try {}里有一个 return 语句,那么紧跟在这个 try 后的 finally {}里的 code 会不会被执行,什么时候被执行,在 return 前还是后?
    BigDecimal 使用 静态方法总结
    成员内部类里面为什么不能有静态成员和方法?
    浅谈多态机制的意义及实现
    Java接口中的成员变量的意义
    IDEA 打包和导入 Jar 包
    Java static关键字
    Java this关键字
    Java 匿名对象
    Java JOptionPane 对话框
  • 原文地址:https://www.cnblogs.com/iwiniwin/p/10793650.html
Copyright © 2011-2022 走看看