33. Search in Rotated Sorted Array(搜索旋转排序数组)两种解法(C++ & 注释)

33. Search in Rotated Sorted Array(搜索旋转排序数组)

1. 题目描述

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

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

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -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

题目链接:中文题目英文题目

2. 二分查找(Binary Search) & 双指针(Two Pointers)

如果原数组没有被旋转,那我们可以直接使用二分法和双指针进行查找。只是现在数组被旋转了,二分法没法直接使用,而双指针的时间复杂度是O(n),不符合题意要求的O(log(n))。所以第一步我们需要考察一下升序数组被旋转之后,有几种情况:

  1. [0,1,2,4,5,6,7] -> [4,5,6,7,0,1,2] ,2和4中间为旋转轴;
  2. [1,3] -> [1,3],1左边为旋转轴或3右边为旋转轴;
  3. [1,3] -> [3,1],1和3中间为旋转轴,注意只有两个数的数组旋转后才会出现降序的效果,这个非常容易被忽略;
  4. [] -> []

以[4,5,6,7,0,1,2]这个为例,如果我们想用二分法来解题,我们可以把数组拆分成两个部分:1)[4,5,6,7];2)[0,1,2],分别进行二分查找即可。也就是我们需要找到数组中的峰顶或谷底,如下图:

在这里插入图片描述
只要找到峰顶或谷底,就可以分别对左右两边的数组进行二分查找,所以需要考察一下怎么找到峰顶或谷底,下面的方法统一找峰顶。我们先定义序号left = 0,right = 数组长度 - 1,mid = (right - left / 2) + left,通过观察我们可以发现:

  1. 如果数组是升序排序,(即nums[left] <= nums[mid] && nums[mid] <= nums[right]),只存在峰顶,那么可以直接使用二分查找,比如[1,2,3,4,5,6];
  2. 如果存在峰顶和谷底,那就不满足升序的特点,且nums[mid - 1] <= nums[mid] && nums[mid] > nums[mid + 1],那mid的位置是峰顶;如果nums[mid - 1] > nums[mid] && nums[mid] <= nums[mid + 1],则mid的位置是谷底;
  3. 如果存在峰顶和谷底,但是mid的位置处于"“山坡”上,即nums[mid - 1] < nums[mid] && nums[mid] < nums[mid + 1],这时我们需要考察一下mid与左右边界值的大小关系,来决定如何移动左右边界;比如[4,5,6,7,0,1,2],如果nums[mid] = 6,那nums[mid] > nums[left] = 4 && nums[mid] > nums[right] = 2,峰顶不可能在mid的左边,所以left = mid + 1,继续1. 查找;同理,如果nums[mid] = 1,峰顶不可能在mid的右边,所以right = mid - 1,继续1. 查找
  4. 如果是两个数,且是降序排序,应该满足nums[mid] <= nums[mid] && nums[mid] > nums[mid + 1],此时mid = 0,所以我们只需修改一下2. 的条件,变成:nums[mid - 1 >= 0 ? mid - 1 : 0] <= nums[mid] && nums[mid] > nums[mid + 1],即可覆盖到这个情况

到此找到了数组的峰顶,就可以直接使用二分法或双指针进行分段查找。

更新:本题代码可以进行简化,有兴趣的童鞋可以参考这里哒 - 81. Search in Rotated Sorted Array II(搜索旋转排序数组 II)两种解法(C++ & 注释)

3. 实例代码

class Solution {
    // binarySearch 和 twoPointers二选一即可
    int binarySearch(vector<int>& nums, int left, int right, int target) {
        if (left == right) { if (nums[left] == target) return left; else return -1; }
        else if (left > right) return -1;
        int mid = (right - left) / 2 + left;
        if (nums[mid] == target) return mid;
        else if (nums[mid] < target) return binarySearch(nums, mid + 1, right, target);
        return binarySearch(nums, left, mid - 1, target);
    }

    int twoPointers(vector<int>& nums, int left, int right, int target) {
        while (left <= right) {
            if (nums[left] == target) return left;
            if (nums[right] == target) return right;
            left++;
            right--;
        }

        return -1;
    }

    int findPeak(vector<int>& nums, int left, int right) {
        int mid = (right - left) / 2 + left;
        
        // 这个数组是升序排列
        if (nums[left] <= nums[mid] && nums[mid] <= nums[right]) return -1; 
        // 找到峰顶和相邻的谷底
        if (nums[mid - 1 >= 0 ? mid - 1 : 0] <= nums[mid] && nums[mid] > nums[mid + 1]) return mid;
        if (nums[mid - 1] > nums[mid] && nums[mid] <= nums[mid + 1]) return mid - 1;
        if (nums[left] <= nums[mid] && nums[mid] > nums[right]) return findPeak(nums, mid + 1, right);
        return findPeak(nums, left, mid - 1);
    }

public:
    int search(vector<int>& nums, int target) {
        if (!nums.size()) return -1;
        int len = nums.size(), peak = findPeak(nums, 0, len - 1);
        if (peak == -1) { 
            return twoPointers(nums, 0, len - 1, target);
            //return binarySearch(nums, 0, len - 1, target); 
        }
        else if (nums[0] <= target && target <= nums[peak]) { 
            return twoPointers(nums, 0, peak, target);
            // return binarySearch(nums, 0, peak, target);
        }
        else if (nums[peak + 1] <= target && target <= nums[len - 1]) { 
            return twoPointers(nums, peak + 1, len - 1, target);
            // return binarySearch(nums, peak + 1, len - 1, target); 
        }
        return -1;
    }
};

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值