• LeetCode高频题33. 搜索旋转排序数组


    LeetCode高频题33. 搜索旋转排序数组

    提示:本题是系列LeetCode的150道高频题,你未来遇到的互联网大厂的笔试和面试考题,基本都是从这上面改编而来的题目
    互联网大厂们在公司养了一大批ACM竞赛的大佬们,吃完饭就是设计考题,然后去考应聘人员,你要做的就是学基础树结构与算法,然后打通任督二脉,以应对波云诡谲的大厂笔试面试题!
    你要是不扎实学习数据结构与算法,好好动手手撕代码,锻炼解题能力,你可能会在笔试面试过程中,连题目都看不懂!比如华为,字节啥的,足够让你读不懂题
    在这里插入图片描述


    题目

    整数数组 nums 按升序排列,数组中的值 互不相同 。

    在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

    给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

    你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

    来源:力扣(LeetCode)
    链接:https://leetcode.cn/problems/search-in-rotated-sorted-array
    著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


    一、审题

    示例 1:

    输入:nums = [4,5,6,7,0,1,2], target = 0
    输出:4
    示例 2:

    输入:nums = [4,5,6,7,0,1,2], target = 3
    输出:-1
    示例 3:

    输入:nums = [1], target = 0
    输出:-1

    提示:

    1 <= nums.length <= 5000
    -104 <= nums[i] <= 104
    nums 中的每个值都 独一无二
    题目数据保证 nums 在预先未知的某个下标上进行了旋转
    -104 <= target <= 104


    二、解题

    可能旋转了,而且k是不告诉你的
    在这里插入图片描述
    也可能没有转过
    但是你仍然要用二分法log(n)找到一个target

    这里考的就是条件分类

    注意:nums 中的每个值都 独一无二

    而啥时候L–M–R上寻找target可以用二分法???
    就是当L,M,R处数字有一个与别的数不相等就行,当L=M=R时,没有办法用二分法的

    在这里插入图片描述
    本题又说了注意:nums 中的每个值都 独一无二
    就是任意位置[L],[M],[R]绝不可能相等
    所以就可以二分

    但是我们就要学,万一nums有重复值怎么办????

    不妨设f就是二分查找arr的L–R上的target,能找到返回下标,不能就返回-1!

    先看M,如果M就是target,直接返回M就行,说明找到了……

    但是上面不行,则下面,一定是target!=[M]

    如果你看[L],[M],[R]三者是不是不全相等,不全相当就可以继续二分查找!

    但是先不看[L],[M],[R]三者是不是不全相等这种情况,咱们先讨论[L],[M],[R]三者全相等的情况:
    [L]=[M]=[R]!=target

    比如:2 2 2 2 2 1 2
    实际上就是1 2 2 2 2 2 2旋转来的
    求target=1,怎么求???
    最开始L=0,R=N-1=6,M=(L+R)/2=3
    你会发现
    在这里插入图片描述
    这种情况压根没法二分!

    咋搞呢
    L++,直到L不是[M]那个数了,就可以在L–R上二分了,满足了不全相等的条件
    在这里插入图片描述
    但是,有可能你一直L++都发现[L]=[M],最后L=M时,咋搞???
    说明L–M全是[M],而且都不是target【target!=M】,显然,你得去M+1–R上二分
    在这里插入图片描述
    可是你发现……可能L一直挪挪挪动到M时,走过的距离是O(N/2),这都o(n)复杂度了????
    ????
    ????
    没办法,题目虽然叫你写o(log(N))的复杂度,但是没用办法绝对
    这是最可恶的出题者!!!

    一般来讲,可能测试案例就没有这种o(n)的事情

    大多情况测试案例都是能迅速找到可以二分的L的…………
    很尴尬吧,很尴尬!

    好,上面我们说,只要当L遇到和[M]不相等时,就是不全相等【不论此时L=M,还是不等于M】,可以在L–R上二分了
    区别就是L!=M时,可以直接看不全相等的分支,
    但是L=M时,需要让L=M+1,再去从头看右半部分,回去调用f,为啥,因为你还没有找到[L],[M],[R]不全相等的情况呢,还得继续找去

    综合就是代码:
    在这里插入图片描述
    上面的continue就是先让L=M+1,再看

    这种L,M,R不全相等的二分具体该如何找到target呢?此时M!=target,是前提条件哦!

    不全相等,就可以把条件分开,尽量找到target所在的一个有序区间
    有几种情况

    (1)可能是[L]!=[M]
    (2)可能是[M]!=[R]

    这都是不全相等的状况
    (1)可能是[L]!=[M]
    如果此时[L]>[M]
    为什么会出现[L]>[M]???
    还不是因为你M–R被旋转到后面了,当然,不论如何我们的arr如何旋转,旋转之后M–R是有序的
    在这里插入图片描述
    比如:下面这种,5>1显然就是M–R被旋转到后面了
    现在其实很简单,此刻从M–R一定是升序,这个你认可的吧,就两部分!但M–R都是有序的
    所以你只需要看target在哪边就行,然后接着去那边二分查找即可
    在这里插入图片描述
    相反,如果[L]<[M],说明L–M还没有被旋转到后面,L–M之间必定有序,本身题目就说了是升序的
    在这里插入图片描述
    所以你看看target是在L–M上呢还是M+1–R上,继续去二分查找

    就像下面这样:
    在这里插入图片描述

    (2)不全相等的话可能是[M]!=[R]
    这时候你发现,如果[M]<[R],显然M–R上有序,如果target在这范围内,去这里二分查找,不行就L–M-1上二分
    在这里插入图片描述

    如果[M]>[R],显然就是因为被旋转动过的,L–M-1一定是有序的,所以如果target在L–M-1上,直接去二分就行,不行就M–R上二分
    在这里插入图片描述

    总之,这么做的目的,就是为了尽可能让[L],[M],[R]尽可能不全相等,然后直接利用二分查找
    在找的时候,通过条件尽快定位target

    就像下面:
    在这里插入图片描述

    代码不需要记忆,只需要记住:[L],[M],[R]不全相等,可直接利用二分查找,如果全相等,挪动L,让他们仨,不全相等!

    你搞懂这个核心思想:不全相等的L,M,R才能直接二分,就好办
    所以有了这个条件,手撕代码它很好确定
    随意写,不一定要跟我的代码相同,只要每次二分边界确定好就行

            //复习:
            //只需要记住:[L],[M],[R]不全相等,可直接利用二分查找,如果全相等,挪动L,让他们仨,不全相等!
            public int searchReview(int[] nums, int target) {
                if (nums == null || nums.length == 0) return -1;
    
                int L = 0;
                int R = nums.length - 1;
    
                //想尽一切办法二分查找
                while (L <= R){
                    int M = L + ((R - L) >> 1);
                    if (nums[M] == target) return M;//位置找到了返回
                    //nums[M] != target
                    //如果[L],[M],[R]全相等,挪动L,让他们仨,不全相等!
                    if (nums[M] == nums[L] && nums[M] == nums[R]){
                        while (nums[L] == nums[M] && L < M) L++;//L最多撞到M,然后退出
                        //如果L!=M,说明提前nums[L] != nums[M] 也!=target,那就满足不全相等,可以下面操作二分
                        if (L == M){
                            //如果L==M了,说明左边压根不可能有target,下次去M+1--R上继续二分
                            L = M + 1;
                            continue;//下面就别操作了
                        }
                    }
    
                    //到了这,[L],[M],[R]不全相等,且nums[M] 也!=target
                    //既然是不全相等,看看是[L] != [M],还是[R] != [M],不论怎样,咱们都要二分,只不过看target在哪?
                    if (nums[L] != nums[M]){
                        //此时再看[L] > [M]还是 [L] < [M]
                        if (nums[L] > nums[M]){//说明L被旋转到前面了,而M+1--R是一定有序的,旋转到后面了
                            if (nums[M + 1] <= target && target <= nums[R]) L = M + 1;//target在右边
                            else R = M;//否则target就在左边
                        }else {//nums[L] < nums[M],说明L--M有序的
                            if (nums[L] <= target && target <= nums[M]) R = M;//target在左边
                            else L = M + 1;//否则target就在右边
                        }
    
                    }else {//[R] != [M]
                        //此时再看看[M] < [R] 还是 [M] > [R]
                        if (nums[M] < nums[R]){//说明M--R有序
                            if (nums[M] <= target && target <= nums[R]) L = M + 1;//target在右边
                            else R = M - 1;//否则target就在左边
                        }else {//nums[M] > nums[R],说明L--M上有序
                            if (nums[L] <= target && target <= nums[M]) R = M + 1;//target在左边
                            else L= M + 1;//否则target就在右边
                        }
                    }
                }
    
                //一直二分没有,L>R,
                return -1;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    测试:

        public static void test(){
            int[] arr = {4,5,6,7,8,1,2,3};
            int target = 8;
            Solution solution = new Solution();
            System.out.println(solution.search(arr, target));
            System.out.println(solution.searchReview(arr, target));
        }
    
        public static void main(String[] args) {
            test();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    问题不大:

    4
    4
    
    • 1
    • 2

    LeetCode测试:
    在这里插入图片描述
    在这里插入图片描述
    老牛逼了!

    反正思想就这么简单,代码你随意敲就行

    如果所有值都不会重复,独一无二出现,那还有一种写法,本身就满足了L,M,R不全相等,就是我上面代码的其中一个小分支,你只需要确定target在哪边就行

    这个代码是上面代码的缩减版本,只满足某一个小分支

    但是上面那个代码是最强大的,因为我们允许上面那个代码有重复值出现!!!

    public int search(int[] nums, int target) {
                //实际上,这要是不旋转,直接就是一个二分查找的问题
                //不过旋转了就得想办法二分查找
                if (nums == null || nums.length == 0) return -1;
                int L = 0;
                int R = nums.length - 1;
    
                while (L <= R){
                    int mid = L + ((R - L) >> 1);//重点,因为要不断更新左边右边
    
                    if (nums[mid] == target) return mid;
                    if (nums[mid] > nums[R]){//右边无序--确实被旋转了,就看target在哪了
                        if (nums[L] <= target && target < nums[mid]) R = mid - 1;
                        else L = mid + 1;
                    }else {//右边有序
                        //显然nums[mid] <= nums[R]
                        if (nums[mid] < target && target <= nums[R]) L = mid + 1;
                        else R = mid - 1;
                    }
                }
                return -1;//如果L越界R都没有找到
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    这方法核心思想其实也是,都不考虑要不全相等,
    在这里插入图片描述


    总结

    提示:重要经验:

    1)[L],[M],[R]不全相等,可直接利用二分查找,如果全相等,挪动L,让他们仨,不全相等!再去二分
    2)本题相当于直接告诉你L,M,R已经不全相等了,就更好写。
    3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

  • 相关阅读:
    03142《互联⽹及其应⽤》各章简答题解答(课后习题)
    姓氏起源查询易语言代码
    关于VO、BO、DTO、PO、Entity、DO的说法
    MySQL的主从同步原理
    使用StreamLoad实现数据同步到StarRocks
    贴片电阻的读数方法
    Vue3自定义指令directives介绍
    京东内网遭开源的“顶级”SpringCloud实战手册,GitHub列为首推
    如何从一门编程语言过渡到另一门编程语言?
    c# 反射专题—————— 介绍一下是什么是反射[ 一]
  • 原文地址:https://blog.csdn.net/weixin_46838716/article/details/125416348