(LeetCode)搜索旋转排序数组——二分法

目录

题目要求

题目理解以及思路分析

代码分部讲解

第一部分

第二部分

第三部分

第四部分

彩蛋


题目要求

整数数组 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) 的算法解决此问题。

示例 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

来源:力扣(LeetCode)
 

本篇文章将继续介绍一新的算法——二分法,这种方法在某些特殊的情况下可以化繁为简,让人眼前一亮。话不多说,开始讲解。

题目理解以及思路分析

(一) 理解题目之前可能一些初学者会问:什么是二分法?,那我们就来解释解释,二分法就是对一段已知的数据取一半,然后通过对比目标数据与中间值的大小来舍弃左右两段数据的其中一段,这样我们寻找的范围就会大大减少。对剩下的数据仍旧按照二分的方法来排除,想想看每次排除一段数据的一半,跟一点一点寻找哪个更快呢?

(二) 理解了什么是二分法后,我们便来分析分析题目。众所周知,使用二分法的先决条件是已知数据按升序或者降序排列 (至于原因请回到上面的解释自行理解理解啦) 。但是很明显题目里给出的数组并不一定是按照升序或者降序排列的 (请注意!这里我用了并不一定是这个字眼,至于为什么,这里先留个引子,下面会单独进行讲解) ,既然不是按照升序或者降序排列的那还能用二分法吗?答案是——能!

(三) 仔细观察题目所给数据的规律,可以发现旋转之后的顺序虽然整体不是升序或者降序排列的,但是在某个断点处的两边分别是按照升序或者降序排列的两段数据。以题目中的数据为例:

这样是不是就理解了?其实很好理解的。OK,既然发现断点两边是有规律的数组,那我们第一次就可以不以数据中间的为分界,而是以断点处为分界。当然只是第一次是这样分界,后面的仍然按照传统的二分法进行分界。

(四) 在介绍二分法的时候就说过了,二分法操作最关键的是做出判断,什么判断呢?要去判断那一边舍去,也就是说明白下一次二分的数据是左边的还是右边的?判断的依据是什么——是目标数据与中间值的大小比较。但是对于本题来说,第一步都是比较特殊的。我们后面结合代码进行详细的讲解。

明白了这些之后还会有些读者不太明白甚至一头雾水,没关系,下面将结合具体的代码进行讲解。

代码分部讲解

第一部分

int search(int* nums, int numsSize, int target){
    int low = 0, high = numsSize - 1, mid = numsSize - 1; //定义
    //寻找断点
    while(nums[0] > nums[numsSize-1] && low < high) //如果数据是旋转后的
      {
        mid = (low + high) / 2;
        if(nums[mid] > nums[low])
            low = mid;
        else
            high = mid;
      }
    

这一步是用来寻找断点的(但是要注意,此时求出的值 mid 代表的是断点的下标而不是断点本身的值)

同时也要注意 high = numsSize - 1 因为下表从 0 开始,因此 high = numsSize - 1 这些细节大家不要忘记

最重要的一点相信很多读者发现了

while(nums[0] > nums[numsSize-1] && low < high)

里面的一个判断条件:nums [ 0 ] > nums [ numsSize-1 ]

为什么这样呢?记不记得前面我的一个字眼——“不一定是”,也就是题目所给的数据并不一定是按照升序或者降序排列的。但是很多读者又有疑惑了?人家题目明明都提示了所有的数据都是旋转后的。但是你们忘记了一点——在预先未知的某个下标上进行了旋转,这就造成了一个特殊情况,旋转点在第一个数字上

举个例子:[ 0,1,2 ] ,令其在下标为 0 的位置上进行旋转,则旋转后的数组仍为 [ 0,1,2 ] ,可见这个时候我们就不能进行那特殊的 第一步,而是按照传统的二分法进行求解。

那有的读者又有疑问了——为什么一个简单的 nums [ 0 ] > nums [ numsSize-1 ] 就可以区分这个数组是不是旋转后的呢?

再来举个例子:

 这样更清晰:只要是这样旋转后的数组,其规律一定是 nums [ 0 ] > nums [ numsSize-1 ]

第二部分

    if(target >= nums[0])
      {
        low = 0;
        high = mid;
      }
    else 
      {
        low = mid + 1;
        high = numsSize - 1;
      }

 这一步就是对于旋转后的数组进行的特殊的 第一步,这一步目的跟传统的二分法进行分界判断是一样的,但是因为起初并不是以中间值为分界点,因此就用到了这个特殊的步骤。

这一步其实是很妙的一步

仅仅用一个 target  >=  nums [ 0 ] ,就把分界判断的步骤给完成了,但是很多读者看到这可能会有疑惑,我这里仍然举个例子就好理解了:

 怎么样是不是很简单?

第三部分

    while(low <= high)
      {
        mid = (low + high) / 2;
        if(target == nums[mid])
          return mid;
        else if(target > nums[mid])
          low = mid + 1;
        else
          high = mid -1;
      }

    return -1;
}

 这一步相对于前几部分就比较简单了,无非就是传统的二分法,分界、判断、再分界直至找到对应的目标数据。这里就不过多讲解,看不懂的可以下面评论或者私信。

第四部分

附上完整代码

int search(int* nums, int numsSize, int target){
    int low = 0, high = numsSize - 1, mid = numsSize - 1;   

    while(nums[0] > nums[numsSize-1] && low < high)
      {
        mid = (low + high) / 2;
        if(nums[mid] > nums[low])
            low = mid;
        else
            high = mid;
      }
    if(target >= nums[0])
      {
        low = 0;
        high = mid;
      }
    else 
      {
        low = mid + 1;
        high = numsSize - 1;
      }

   
    while(low <= high)
      {
        mid = (low + high) / 2;
        if(target == nums[mid])
          return mid;
        else if(target > nums[mid])
          low = mid + 1;
        else
          high = mid -1;
      }

    return -1;
}

彩蛋

感谢你看完了上面的讲解,学到了新的知识,其实更多的读者看见这道题第一思路就是一个一个判断,我也一样。

为此在这里附上代码作为彩蛋

int search(int* nums, int numsSize, int target){
    int i;
    for(i = 0;i < numsSize;i ++)
      {
        if(nums[i] == target)
          {
            return i;
          }
      }
    return -1;
}

这个思路固然简单,但是它的时间复杂度为 O(n),不符合题目要求。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

简十三

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

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

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

打赏作者

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

抵扣说明:

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

余额充值