剑指 Offer 11. 旋转数组的最小数字 我发现了另一个能收缩查找区间的条件,原来mid与左右端点都可比

剑指 Offer 11. 旋转数组的最小数字

在这里插入图片描述

WA,寄了

l++ 和 r–并不是o(logn)复杂度,而是(n),比如 10 折半直接折掉了 5 个数,而 l++, r-- 只能折 2 个数,悟了悟了,大家看个乐呵就行

起因

l 为左端点, r 为右端点

我刚拿到这道题也懵了,排序数组得用二分查找来收缩查找区间啊,可是旋转之后咋用二分来收缩区间呢?

于是广快去题解区拜读大佬们的文章,于是又又又又看到了笑脸路飞大佬的文章 ,写的那是相当不错啊,尤其是关于用mid与右端点做比较才能收缩区间的讲解

不过当numbers[r]==numbers[mid]时只能退化到线性查找让我觉得有点不甘心,在我仔细研究之后,我发现原来能用numbers[l] 和 numbers[r]来进一步收缩区间。

numbers[l] 和 numbers[r] 的特点

如果旋转了

如:3, 4, 5, 1, 2

  1. numbers[l] 一定大于等于 numbers[r]

  2. numbers[l] 是左排序数组的最小值,numbers[r] 是右排序数组的最大值

如果没有旋转
  1. numbers[l] 一定小于 numbers[r]

利用到了特点的代码

最小值一定在右排序数组,把 l 移出左排序数组, 把 r 往更小的元素索引位置移动就行了

与右端点比较
class Solution {
    public int minArray(int[] numbers) {
        int l, r;
        l = 0;
        r = numbers.length - 1;
        while(l < r) {
            if(numbers[l] < numbers[r]) {
                // 说明[l, r]没旋转,因此我们直接返回最小值左端点即可
                return numbers[l];
            }
            int mid = l + (r - l) / 2;
            if(numbers[mid] < numbers[r]) {
                // [mid, r] 为右排序数组 r 已经位于右排序数组了,只需要把 r 往更小的元素移动就行了
                r = mid;
            } else if(numbers[mid] > numbers[r]) {
                // 此时 [l, mid] 位于左排序数组把 l 移出左排序数组
                l = mid + 1;
            } else if(numbers[mid] == numbers[r]) {
                if(numbers[l] == numbers[r]) {
                    /* 当 numbers[l]、numbers[r]、numbers[mid] 三值相等,此时我们无法确定最小值在 (l, mid) 还是在 (mid, r) ,
                       因此 l 和 r 指针同时向中间靠拢收缩查找区间
                       
                       为什么可以直接移动 l 和 r指针呢?
                       因为此时 l,mid,r 必然位于两个或3个不同的索引上,不可能为1个,如果是一个,那么l==r, 就退出循环了
                       要么是 [2, ?, 2, ?, 2] 要么是 [2, 2]
                       因此收缩区间可以直接变为 [l+1, r-1],就相当于去掉了两个 2 ,咱们还剩一个 mid 的 2,
                       所以不用担心这样移动会把最小值移出查找区间(万一 2 为最小值的情况)
                     */
                    l++;
                    r--;
                } else if(numbers[l] > numbers[r]) {
                    /* 这说明咱们的数组肯定经过了旋转
                       注意此时 numbers[mid] == numbers[r]
                       因为 numbers[r] 是右排序数组的最大值,所以 [mid, r] 必然是重复的元素
                       因此收缩 r, 但是别收缩的太猛,万一这个重复元素就是最小值呢。
                       所以 r=mid 就够了(因为我们条件是 l==r 就可以退出循环,然后返回 numbers[l] 所以不管是 l 还是 r 都可以取到这个最小值)
                     */
                    r = mid;
                }
            }
        }
        return numbers[l];
    }
}
与左端点比较

难点就是有重复的元素,如果我们能去重,那自然也能用左端点来收缩区间

class Solution {
    public int minArray(int[] numbers) {
        int l, r;
        l = 0;
        r = numbers.length - 1;
        while(l < r) {
            if(numbers[l] < numbers[r]) {
                // 说明[l, r]未旋转
                return numbers[l];
            }
            int mid = l + (r - l) / 2;
            if(numbers[l] > numbers[mid]) {
                // 说明 [mid, r] 是右排序数组,因此移动 r 到更小的 mid 处
                r = mid;
            }else if(numbers[l] < numbers[mid]) {
                // 说明 [l, mid] 是左排序数组,因此把 l 移出已经判断为左排序数组的区间(即[l, mid])
                l = mid + 1;
            }else if(numbers[l] == numbers[mid]) {
                if(numbers[l] == numbers[r]) {
                    l++;
                    r--;
                }else if(numbers[l] > numbers[r]) {
                    /* 此时 numbers[l] == numbers[mid] , 并且 numbers[l] > numbers[r] 说明数组经过了旋转
                       那么 [l, mid] 肯定是重复的元素
                       如 [2, 2, 7, 1, 1] , 此时即使我们移动 l 到 mid+1 的位置也不会把最小值移出查找区间(mid + 1 可能在左排序数组,也可能在右排序数组了)
                     */
                    l = mid + 1;
                }
            }
        }
        return numbers[l];
    }
}

回答一些问题

为什么 while 循环条件是 l < r ?

因为返回结果是 numbers[l], 而我们 r 初始值为 numbers.length - 1,万一这个 r 刚好位于最小值上呢

你要是循环条件为 l <= r,也就是说 l == r + 1 时, 才能退出循环,岂不是一开始就把人家排除了。

彩蛋

在这里插入图片描述
在这里插入图片描述
hahaha,我居然吃透了一道 hard 题

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值