每日一题 - 11. 旋转数组的最小数字

题目信息

  • 时间: 2019-07-23

  • 题目链接:Leetcode

  • tag: 二分查找

  • 难易程度:简单

  • 题目描述:

    把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

示例1:

输入:[3,4,5,1,2]
输出:1

示例2:

输入:[2,2,2,0,1]
输出:0

解题思路

本题难点

排序数组的旋转,找到旋转点,性能最佳。

具体思路

寻找旋转数组的最小元素即为寻找 右排序数组 的首个元素 numbers[x] ,称 x为 旋转点

排序数组的查找问题首先考虑使用 二分法 解决,其可将遍历法的 线性级别 时间复杂度降低至 对数级别

  • 循环二分:
    1. 当 numbers[m] > numbers[j]时: m 一定在 左排序数组 中,即旋转点 x 一定在 [m+1,j] 闭区间内,因此执行 i=m+1;
    2. 当 numbers[m] < numbers[j] 时:m 一定在 右排序数组 中,即旋转点 x 一定在[i,m] 闭区间内,因此执行 j=m;
    3. 当 numbers[m] == numbers[j] 时: 无法判断 m 在哪个排序数组中,即无法判断旋转点 x 在 [i,m] 还是 [m+1,j] 区间中。解决方案: 执行 j=j−1 缩小判断范围 。

展开分析 numbers[m] == numbers[j] 情况

  • 无法判定 m 在左(右)排序数组: 设以下两个旋转点值为 0 的示例数组,则当 i=0, j=4 时 m=2 ,两示例结果不同。

    • 例 [1,0,1,1,1] :旋转点 x=1 ,因此 m=2 在 右排序数组 中。

    • 例 [1,1,1,0,1] :旋转点 x=3 ,因此 m=2 在 左排序数组 中。

  • j=j−1 操作的正确性证明:只需证明每次执行此操作后,旋转点 x 仍在 [i,j] 区间内即可。

    • 若 m 在右排序数组中: numbers[m] == numbers[j] ,因此数组 [m,j](恒有 m<j)区间内所有元素值相等,执行 j=j−1 只会抛弃一个重复值,因此旋转点 x 仍在 [i,j] 区间内。

    • 若 m 在左排序数组中: 由于 左排序数组 任一元素 >= 右排序数组 任一元素 ,因此可推出旋转点元素值 numbers[x] <= numbers[j] == numbers[m],则有:

      1. 若 numbers[x] < numbers[j] : 即 j 左方仍有值更小的元素,执行 j=j−1 后旋转点 x 仍在 [i,j] 区间内。

      2. numbers[x] == numbers[j] 分为以下两种情况。

        当 j>xj>x : 易得执行 j=j−1j=j−1 后旋转点 xx 仍在 [i,j] 区间内。

        当 j=x: 特殊情况,即执行 j=j−1 后旋转点 x 可能不在 [i,j] 区间内。例如 [1,1,1,2,3,1] ,当 i=0 , m=2 , j=5 时执行 j=j−1 后虽然 丢失了旋转点索引 x=5 ,但最终返回值仍正确(最终返回的 numbers[0] 等于旋转点值 numbers[5] ),这是因为:之后的二分循环一直在执行 j=m ,而区间 [i,m] 内的元素值一定都等于旋转点值 numbers[x] ( ∵ 区间内元素值既要满足 ≥ 也要满足 ≤ numbers[x]) ,因此 仍可保证正确的返回值 。

提示 是否可以用 numbers[m]numbers[i] 比较做代替?不可以。因为做比较的目的是判断 m 在哪个排序数组中。但在 numbers[m] > numbers[i]情况下,无法判断 m 在哪个排序数组中。本质是因为 j 初始值肯定在右排序数组中; i 初始值无法确定在哪个排序数组中。

代码

class Solution {
    public int minArray(int[] numbers) {
        int l = 0;
        int r = numbers.length-1;
        while(l < r){
            int mid = (l + r)/2;
            if(numbers[mid] > numbers[r]){
                l = mid + 1;
            }else if(numbers[mid] < numbers[r]){
                r = mid;
            }else{
                r--;
            }
        }
        return numbers[l];
    }
}

复杂度分析:

  • 时间复杂度 O(logN) : 在特例情况下(例如 [1,1,1,1]),会退化到 O(N)。
  • 空间复杂度 O(1) : i , j , m指针使用常数大小的额外空间。

posted @ 2020-07-23 19:28  小锵同学、  阅读(121)  评论(0编辑  收藏  举报