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

 

 

文章目录

  • 题目描述
    • 解法1
      • 实现代码
    • 二分查找
    • 解法2
      • 实现代码
    • 一点想法

 

题目描述

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{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}为例,可以分成两个有序非减数组来看待,如下图所示
【剑指Offer】旋转数组的最小数字_第1张图片
显然,数组的最小值就在两个非减数组的交界处,同时由于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

你可能感兴趣的:(【剑指Offer】旋转数组的最小数字)