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;
        }

测试:

    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();
    }

问题不大:

4
4

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰露可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值