【小白爬Leetcode33】6.2 搜索旋转排序数组 Search in Rotated Sorted Array

【小白爬Leetcode33】6.2 搜索旋转排序数组 Search in Rotated Sorted Array

Leetcode33 m e d i u m \color{#FF4500}{medium} medium
点击进入原题链接:Leetcode33 搜索旋转排序数组 Search in Rotated Sorted Array

题目

Discription

Suppose an array sorted in ascending order is rotated at some pivotunknown to you beforehand.

(i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]).

You are given a target value to search. If found in the array return its index, otherwise return -1.

You may assume no duplicate exists in the array.

Your algorithm’s runtime complexity must be in the order of O(log n).
在这里插入图片描述

中文描述

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。
在这里插入图片描述

思路 二分搜索的变种

设旋转点为pivot

这道题的难点在于发现一个规律

虽然遭到旋转,但旋转点pivot的前后都是升序的数组,进一步推断,在旋转后的数组中任选一个元素ii的一边若是乱序的数组(包含pivot的一变),那么另一边一定是升序的数组。那么,把i具体化为mid(数组中点)自然也不例外,这就为我们改良二分法提供了一个理论支持。

比较直观的一种实现:

分开讨论target<nums[mid]target>nums[mid]的情况:
1.target<nums[mid]
首先讨论 target<nums[mid]的情况,如果按照一般的二分搜索,肯定要在数组的前半段搜索,然后这里我们处理的可能是一个旋转数组,因为要讨论pivot点落在哪一边。

  • if(nums[begin]<nums[mid]),那么说明数组的前半段升序,那么pivot点必落在后半段。此时:
    if(target>nums[begin]),说明target点落在前半段升序数组里,那么在前半段继续二分搜索;
    if(target==nums[begin]),那这个begin就是我们要找的结果;
    if(target<nums[begin]),说明target点落在了后半边的无序数组内,因此在后半段继续二分搜索。
  • if(nums[begin]>nums[mid]),那么说明数组的前半段无序,即pivot点必落在前半段,那么后半段必然升序,也就是全部大于nums[mid],而target<nums[mid],那么搜索空间只能在前半段。
  • if(nums[begin] == nums[mid]),这是一种很特殊的情况,意味着数组里只有2个或1个数字,此时已知target<nums[mid],那么要想找到target,数组只可能是[big,small]这样的情况,即大的在前,小的在后,而target==small。考虑到这种情况,令begin = mid+1即可。
  • 如果经过二分搜索没有找到target,按照题意返回-1即可。

2.target>nums[mid],这种情况和1是一样的道理;
3.target==nums[mid] ,和普通的二分搜索一样,这表示找到了target元素,直接return mid即可。

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int begin = 0;
        int end = nums.size()-1;
        while(begin<=end){
            int mid = (begin+end)/2;
            if(target == nums[mid]) return mid;
            if(target<nums[mid]){//目标值小于当前的中心值的情况
                if(nums[begin]<nums[mid]){ //如果前半段是升序的
                    if(target>nums[begin]) end = mid-1;
                    else if(target==nums[begin]) return begin;
                    else //target<nums[begin]的情况,那么肯定在后半部分旋转部分
                        begin = mid+1;
                }
                else if(nums[begin]>nums[mid]){ //如果前半段无序
                    end = mid-1;
                }
                else//nums[begin]==nums[mid] 小于等于2个元素的特殊情况
                    begin = mid+1; //如果要能找到只可能是[7,1]这种前面比后面大的情况
            }
            if(target>nums[mid]){ //目标值小于当前的中心值的情况和上面基本类似
                if(nums[begin]<nums[mid]){
                    begin = mid+1;
                }
                else if(nums[begin]>nums[mid]){
                    if(target<nums[end]) begin = mid+1;
                    else if(target==nums[end]) return end;
                    else end = mid-1;
                }
                else begin = mid+1;
            }
        }
        return -1;
    }
};

在这里插入图片描述

比较简洁的一种实现:

上面的方法将target<nums[mid]target>nums[mid]分成了两种情况,其实二者可以合并为target!=nums[mid]的情况,只要换一个角度想即可。
不妨把关注点放在数组的前半部分还是后半部分升序上:

  • 若前半部分升序,那么考察target是否落在前半段,即if (nums[0] <= target && target < nums[mid]),若是则在前半段搜索,若不是则是后半段搜索;
  • 若后半部分升序,那么考察target是否落在后半段,即if (nums[mid] < target && target <= nums[n - 1]),若是则在后半段搜索,若不是则是前半段搜索;

下面放上我注释后的官方解答代码

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n = (int)nums.size();
        if (!n) return -1; //如果是空数组
        if (n == 1) return nums[0] == target ? 0 : -1; //只有一个元素的数组直接判断就好
        int l = 0, r = n - 1; //定义左右搜索端点
        while (l <= r) {
            int mid = (l + r) / 2;
            if (nums[mid] == target) return mid;
            if (nums[0] <= nums[mid]) { //如果是前半段升序
                if (nums[0] <= target && target < nums[mid]) { //如果target在前半段之间
                    r = mid - 1; //应该在前半段搜索
                } else { //否则应该在后半段
                    l = mid + 1;
                }
            } else { //如果是后半段升序
                if (nums[mid] < target && target <= nums[n - 1]) { //如果target在后半段之间
                    l = mid + 1;  //应该在后半段搜索
                } else { //否则应该在前半段搜索
                    r = mid - 1;
                }
            }
        }
        return -1;
    }
};

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值