剑指Offer-06-旋转数组的最小数字

题目

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

解析

其实这里考察的查找,而经典的查找不外乎有二分查找,二叉排序树,二叉平衡树,红黑树或者哈希查找之类的。所以我们遇到了查找问题,往这几个方向想准没错。
1. 如果查找的数组是有序的或者部分有序的,考虑二分
2. 如果要求O(1)查找到指定值,那么采用哈希思想即可。

而这道题是有序数组的变形,即为一个有序数组的变形。但是我们仍然可以尝试使用二分来做。仔细观察旋转数组会发现该数组有如下几个特点:
1. 按照旋转的定义,原数组的最小的元素会被放到后面,由这个最小的元素可以把数组分成2部分,前一部分是非递减数组,而后一部分同样也是非递减数组。所以我们尝试按照二分查找的思想,用两个指针low,high分别指向数组的开头的和结尾,这样我们求出mid = low + (high - low) / 2
2. 当left <= mid时,我们可知mid是处于数组的第一部分,因为我们在第一条中已经说了数组分为了2个非递减数组。这时说明最小的元素不在[left,mid]区间中,而是在[mid,right]区间中,所以令left = mid
3. 当mid <= right时,我们可知mid是处于数组的第二部分,因为我们在第一条中已经说了数组分为了2个非递减数组。这时说明最小的元素不在[mid,right]区间中,而是在[left,mid]区间中,所以令right = mid
4. 正是上面的做法,才导致了left总是指向数组第一部分的最大值,而right总是指向数组第二部分的最小值。所以当right - left == 1时,即可结束查找,right指向就是我们所要求的最小值。

很容易写出如下代码:

    public static int minNumberInRotateArray(int [] array) {
        if(array.length == 0) {
            return 0;
        }
        int low = 0;
        int high = array.length - 1;
        while(array[low] >= array[high]) {
            if(high - low == 1) {
                break;
            }
            mid = low + (high - low) / 2;
            if(array[low] <= array[mid]) {
                low = mid;
            }else if(array[mid] <= array[high]) {
                high = mid;
            }
        }
        return array[right];
    }

特例

虽然上诉算法很简单且有效,但是任何问题都有它自己的独特的大坑。我们忘了考虑两种特例。
1. 数组就是原数组本身,旋转长度为0,这样最小的元素应该是low指向的元素啊
2. 如果是如这种数组3,1,3,3,3。这种情况left == mid == right, 按照上面的算法,我们区间会缩为后半区间,其实不然,最小元素1在前半数组中。所以遇到这种情况,只能对[low,high]区间进行顺序查找。

     public static int minNumberInRotateArray(int [] array) {
        if(array.length == 0) {
            return 0;
        }
        int low = 0;
        int high = array.length - 1;
        int mid = low;
        while(array[low] >= array[high]) {
            if(high - low == 1) {
                mid = high;
                break;
            }
            mid = low + (high - low) / 2;
            /**
             * 我知道剑指Offer中是让按顺序查找,那么这样复杂度不还是O(n)吗?
             * 所以按照划分子问题的方法,根据mid划分2个子分组
             * 然后对这两个子数组继续使用我们的二分即可呀!
             * 采用了尾递归,不存在栈溢出,同时避免了顺序查找
             * 优化:当然你可以令开一个函数,记录区间,避免我的数组复制的开销
             * 我tm沙比了,这tm的复杂度飙到了O(nlogn)了
             */
//            if(array[low] == array[mid] && array[mid] == array[high]) {
//                int[] left = new int[mid - low + 1];
//                int[] right = new int[high - mid + 1];
//                System.arraycopy(array, low, left, 0, left.length);
//                System.arraycopy(array, mid, right, 0, right.length);
//                return Math.min(minNumberInRotateArray(left), minNumberInRotateArray(right));
//            }
            if(array[low] == array[mid] && array[mid] == array[high]) {
                return ordinalGetMin(array, low, high);
            }
            if(array[low] <= array[mid]) {
                low = mid;
            }else if(array[mid] <= array[high]) {
                high = mid;
            }
        }
        return array[mid];
    }

    public static int ordinalGetMin(int[] array, int low, int high) {
//        int min = Integer.MAX_VALUE;
//        for(int i = low; i <= high; i++) {
//            min = Math.min(min, array[i]);
//        }
//        return min;
        for(int i = low + 1; i <= high; i++) {
            if(array[i] < array[i - 1]) {
                return array[i];
            }
        }
        return array[low];
    }

猜你喜欢

转载自blog.csdn.net/dawn_after_dark/article/details/80684500