zoukankan      html  css  js  c++  java
  • 【力扣算法】数组(5): 搜索旋转排序数组

    原题说明:假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。你可以假设数组中不存在重复的元素。你的算法时间复杂度必须是 O(log n) 级别。

    原题链接:https://leetcode-cn.com/problems/search-in-rotated-sorted-array


    题目分析

    从时间复杂度上看,满足条件的解题算法应该就是二分查找。牛人有自己的快捷步骤,但是我的想法是分步骤、慢慢来。

    后来在测试一个实例时,我才知道二分查找是基于数列有序的前提下,所以对于这样一个“旋转”的数组,需要先找到旋转点,然后以旋转点为参考,选中其中一段进行二分查找

    换言之,题目设置的“旋转”障碍,就是将一个有序数组分成了两段,使得直接使用二分查找不可行

    故解题也分成两部分讨论:

    PART1→找到旋转点Rotation index

    PART2→根据旋转点找到可能对应的区间(因为可能不存在),再进行二分查找

    PART1:找到旋转点

    这次直接分析源码

     1 private int find_rotation(int[] nums, int left, int right) {
     2     if(nums[left]<nums[right])
     3         return 0;
     4     
     5     int rotation;
     6     int mid = (left+right+1)>>1;
     7     while(left<right) {
     8         if(nums[mid]<nums[mid-1])
     9             return mid - 1;
    10         else {
    11             if(nums[mid]<nums[left]) {
    12                 right = mid - 1;
    13                 mid = (left+right+1)>>1;
    14             }
    15             else {
    16                 left = mid;
    17                 mid = (left+right+1)>>1;
    18             }
    19         }
    20     }
    21     return 0;
    22 }

     1. 根据实际测试,数组$nums$是有可能不旋转的,即整体是一个有序数列(递增数列)。在这样的情况下,旋转点不存在、Rotation index为0(这也是通过实例测试得知的)。故有了第2、3行代码。

    2. 第6行分析。这应该算是我第2次在力扣上做二分查找了,第6行的代码也可以写成这样int mid = left + (right - left + 1) / 2;这样的话,就保障查找的时候靠近左端。这里要说明的是为什么不采用int mid = (left + right) / 2;这样的方式——因为担心$n u m s[l e f t]$+$n u m s[r i g h t]$的和会溢出。那么写成第6行的格式,是因为位运算效率更高(我也不知道如何得出这样结论的),其中$>>1$作为右移位运算符,由于二进制,右移一位就相当于除于2。

    3. 对于循环条件,可以这样理解,当$left=0, right=1$时,$mid=(0+1+1)/2=1$,此时$target$只可能等于或者小于(递增数列)$n u m s[m i d]$。若小于,就只能让$mid$取$left$,唯一的办法就是$right=mid-1$,此时$left==right$;或者直接终止循环,比较$target$和$n u m s[l e f t]$的大小(本题采用第二种)。也就是说,$left<right$是可以取到所有情况的,且不会数组越界。【新的分析见数组(6)】

    4.第8、9行原来是这么写的:

    if(nums[mid]>nums[mid+1])//7,8,9,1,2,3
      return mid;

    从实例上看,当然没错,可要命的是出现两个元素的数组。同3中说的,此时$mid==right=1$,如果再出现$mid+1$,就会数组越界。所以要反过来写判断条件,不过要注意,这样跟着返回值也应该修改

    5.最后是两端移动。由于左端固定,右端如果每次取值都是$mid$,就会发生3中的情况,最后$mid$和$right$都是1,陷入死循环。考虑到每次循环中,$mid$对应的值都已经被比较过了,所以取$right = mid - 1$。

    6.这里有一个问题,之前3中说到,最后要比较$target$和$n u m s[l e f t]$的大小,那么为什么循环结束后都没有体现呢?因为找旋转点是二分查找的变形,如果$left$是旋转点,那么必然有$nums[mid]<nums[mid-1]==nums[left]$。换言之,此时,就已经满足了第8行的判断条件,返回旋转点$mid-1$(此时$left<mid$,所以不会越界)。而当$right==left$时,已经说明数列是递增的、或者是单元素数列,所以直接返回$0$即可

    PART2:二分查找

    先看代码

     1 private int binary_search(int[] nums, int left, int right, int target) {
     2     int index;
     3     int mid = (left+right+1)>>1;
     4     while(left<right) {
     5         if(nums[mid]==target)
     6             return index = mid;
     7         else if(nums[mid]>target) {
     8             right = mid-1;
     9             mid = (left+right+1)>>1;
    10         }
    11         else if(nums[mid]<target) {
    12             left = mid;
    13             mid = (left+right+1)>>1;
    14         }
    15     }
    16     return index=(target == nums[left])?left:-1;//left is close, which makes the mid cannot get the value of left
    17 }

     关于二分查找的解释,这里就不再说明。主要看第16行,这里面就是PART1第3点说到的情况。比较完$nums[left]$和$target$,就遍历完所有情况了。此时若没有找到,就返回$-1$。

    主函数:

     1 public int solution(int[] nums, int target) {
     2     if(nums.length<1 || nums == null)
     3         return -1;
     4 
     5     int left = 0, right = nums.length - 1, mid = (left+right+1)>>1;
     6     int rotation = 0;
     7     int index;
     8     
     9     rotation = find_rotation(nums, left, right);
    10     
    11     if(target>=nums[0]&&target<=nums[rotation]) 
    12         index = binary_search(nums, 0, rotation, target);
    13     else
    14         index = binary_search(nums, rotation, nums.length-1, target);
    15     return index;
    16 }

     第9行就是PART1,第11行到第14行就是PART2。


    总结

    这道题目非常恶心。在于它给的实例有不旋转的情况,也在于我对二分查找的边界不了解。但是如果能够耐心地在边界处推导一下,也没有那么难。

    • 要注重实例的探索;
    • 要注重边界处的推导。
  • 相关阅读:
    一个给照片换底色的牛逼网站
    如何写好ppt
    当你迷茫时,就来看看这个视频!
    IPV6地址检测
    性能测试流程
    pycharm中无法以pytest运行
    Mac charles配置完成后无法上网
    Mac Mysql安装过程的各种坑
    python常用模块之——正则re模块
    Jmeter参数化真香
  • 原文地址:https://www.cnblogs.com/RicardoIsLearning/p/12059311.html
Copyright © 2011-2022 走看看