刷题打卡 | 剑指Offer之查找旋转数组中的最小数字

68 阅读4分钟

剑指Offer☔问题06:查找旋转数组中的最小数字

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

问题描述:

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

解题思路:

  思路一:暴力求解。这题可以直接对数组进行一次遍历就可以找到最小值,复杂度为O(n)。

  思路二:推荐。如果整个数组是有序的,那可以考虑用折半查找来实现。 对于旋转数组,我们发现,它实际上可以划分为两个排序的子数组,而且前面数组的元素都不小于后面数组的元素, 并且最小值正好就是这两个数组的分界线,由此,我们可以得出以下的解决方法:

首先用两个指针low和high分别指向数组的第一个元素和最后一个元素, 然后可以找到中间元素mid。对于这个中间元素,有以下两种情况:(1)该元素大于等于low指向的元素,此时最小的元素说明在mid的后面,可以把low=mid;(2)中间元素小于等于high指向的元素,那么最小元素在mid之前,可以high=mid。特别注意:这里不要+1或者-1,因为只有这样才能保证low始终在第一个数组,high始终在第二个数组。 依次循环,当最后low和high相差1时,low指向第一个数组的最后一个,high指向第二个数组的第一个(即为我们要找的最小值)。 很明显,以上查找的时间复杂度为 O(logN)。

  除此之外,本题还有两个特殊情况:

  1. 将数组前 0 个元素移动到后面(相当于没有旋转,数组整体有序)。 明显我们上面的分析没有包含这种情况,需要特殊处理,方法也很简单,将第一个元素和最后一个元素相比, 若第一个元素小于最后一个元素,则说明最小值就是第一个元素,可以直接返回。
  2. 首尾指针指向的数字和中间元素三者都相等时,无法判断中间元素位于哪个子数组,无法缩小问题规模。此时,只能退而求其次,进行顺序查找。

求解:

  在正式求解之前我们需要考虑一些特别情况,例如数组是否为空、数组只有一个数据、是否为数组的旋转等。

// 当数组大小为 0 时,返回 0
        if (tempArray.length == 0){
            return -1;
        }

        // 当数组中只有一个数据时,返回本身
        if (tempArray.length == 1){
            return tempArray[0];
        }

        // 当数组的第一个数据比最后一个数据小时,说明是一个非 “数组的旋转”,返回数组的第一个数
        if (tempArray[0] < tempArray[tempArray.length-1]){
            return tempArray[0];
        }

  方法一:暴力求解。

public static int findMinNumber1(int[] tempArray){
        System.out.println("数组为:" + Arrays.toString(tempArray));

        int min = tempArray[0];
        for (int i = 1; i < tempArray.length; i++) {
            if (min > tempArray[i]){
                min = tempArray[i];
            }
        }
        return min;
    }

  暴力求解的时候需要注意,最小值min初始化的时候不能初始化为0。这点其实很容易被忽略。在查找最小值的时候,可以考虑用数组的第一个值初始化min,在查找最大值得时候可以用0初始化。

  方法二:推荐使用特殊数组的特性。

public static int findMinNumber(int[] tempArray){
        System.out.println("数组为:" + Arrays.toString(tempArray));

        int low = 0;
        int high = tempArray.length - 1;

        while (low < high){
            int mid = (low + high) / 2;

            if (tempArray[low] < tempArray[mid]){ // 当中间数值比 low 位置大时,则将 low 调整到 mid 位置
                low = mid;
            }else if (tempArray[high] > tempArray[mid]){ // 当中间数值比 high 位置小时,则将 high 调整到 mid 位置
                high = mid;
            }

            if (high - low == 1){ // 当 high 和 low 差值 1 时,表明检索完成, high 位就是最小值
                return tempArray[high];
            }

            // 当 low 、high 和 mid 三个位置均相等时,表明数组并非寻常 (例如:1,1,1,1,1),则直接暴力搜索
            if (tempArray[low] == tempArray[mid] || tempArray[high] == tempArray[mid]){
                int min = tempArray[0];
                for (int i = 1; i < tempArray.length; i++){
                    if (tempArray[i] < min){
                        min = tempArray[i];
                    }
                }
                return min;
            }

        }

        return -2;
    }

完整代码:

public static int findMinNumber(int[] tempArray){
        System.out.println("数组为:" + Arrays.toString(tempArray));

        // 当数组大小为 0 时,返回 0
        if (tempArray.length == 0){
            return -1;
        }

        // 当数组中只有一个数据时,返回本身
        if (tempArray.length == 1){
            return tempArray[0];
        }

        int low = 0;
        int high = tempArray.length - 1;
        // 当数组的第一个数据比最后一个数据小时,说明是一个非 “数组的旋转”,返回数组的第一个数
        if (tempArray[0] < tempArray[tempArray.length-1]){
            return tempArray[0];
        }

        while (low < high){
            int mid = (low + high) / 2;

            if (tempArray[low] < tempArray[mid]){ // 当中间数值比 low 位置大时,则将 low 调整到 mid 位置
                low = mid;
            }else if (tempArray[high] > tempArray[mid]){ // 当中间数值比 high 位置小时,则将 high 调整到 mid 位置
                high = mid;
            }

            if (high - low == 1){ // 当 high 和 low 差值 1 时,表明检索完成, high 位就是最小值
                return tempArray[high];
            }

            // 当 low 、high 和 mid 三个位置均相等时,表明数组并非寻常 (例如:1,1,1,1,1),则直接暴力搜索
            if (tempArray[low] == tempArray[mid] || tempArray[high] == tempArray[mid]){
                int min = tempArray[0];
                for (int i = 1; i < tempArray.length; i++){
                    if (tempArray[i] < min){
                        min = tempArray[i];
                    }
                }
                return min;
            }

        }

        return -2;
    }


    public static void main(String[] args) {
        int[] array1 = new int[]{8, 9, 1, 2, 3, 4};
        int[] array2 = new int[]{3, 4, 5, 1, 2};
        int[] array3 = new int[]{3, 4, 5, 1, 2, 3};
        int[] array4 = new int[]{1, 1, 0, 1, 1, 1};
        int[] array5 = new int[]{1, 1, 1, 1, 1, 1};

        int result = findMinNumber(array4);
        if (result == -1){
            System.out.println("数组为空");
        }else if (result == -2){
            System.out.println("检索失败");
        }else {
            System.out.println("数组最小值为:" + result);
        }

    }

结果展示:

01.png