12 在旋转排序数组中查找元素(Search in Rotated Sorted Array)

1 题目

  在旋转排序数组中查找元素(Search in Rotated Sorted Array)

lintcode:题号——62,难度——medium

2 描述

  给定一个有序数组,但是数组以某个元素作为支点进行了旋转(比如,0 1 2 4 5 6 7 可能成为4 5 6 7 0 1 2)。给定一个目标值target进行搜索,如果在数组中找到目标值返回数组中的索引位置,否则返回-1。你可以假设数组中不存在重复的元素。

  名词:

RSA:旋转排序数组,即Rotated Sorted Array

  样例1:

输入:数组 = [4, 5, 1, 2, 3],target = 1
输出:2
解释:

元素1在数组中对应索引位置为2。

  样例2:

输入:数组 = [4, 5, 1, 2, 3],target = 0
输出:-1
解释:

元素0不在数组中,返回-1。

3 思路

  因为RSA是分段有序的,要在RSA里查找某个值,之前有一题是寻找RSA中的最小值1,可以通过找到最小值将RSA分为两段标准的有序数组,然后再进行二分查找来得到结果,这种方式的时间复杂度是两轮二分搜索O(log n) * 2,依旧是O(log n)。
  第二种思路是通过抛掉不可能存在目标元素的区间,来不断缩小目标区间的思想,即“half half”的方式,在RSA数组(假设未旋转前的原数组为升序)的中点切一刀,考虑RSA数组的特点,数组被最小值分为上半区间和下半区间,中点位置有两种情况,第一,中点元素在上半区间(升序RSA的上半区间在左边),这种情况下,如果起点元素 <= 目标元素值 <= 中点元素,则目标元素不可能在中点右边,抛掉右边,反之则抛掉左边;第二,中点元素在下半区间,这种情况下,如果中点元素 <= 目标元素值 <= 结尾元素,则目标元素不可能在中点左边,抛掉左边,反之则抛掉右边。而判断中点在上半区还是下半区的方式,可以通过比较中点元素值与起点元素值或者结尾元素值的大小来得到。使用以上方式来不断缩小目标区间,直到找到目标或者目标元素不存在。使用第二种思路来解题。

  1. 取中点;
  2. 判断中点元素值与结尾元素值的大小,找出中点属于上半区间还是下半区间;
  3. 上半区间:起点元素 <= 目标元素值 <= 中点元素,成立则取左,不成立取右。
  4. 下半区间:中点元素 <= 目标元素值 <= 结尾元素,成立则取右,不成立取左。
  5. 重复以上步骤,直到找到目标或者目标元素不存在。

3.1 图解

输入:数组 = [4, 5, 6, 7, 8, 9, 0, 1, 2, 3],target = 9
输出:5

元素9在数组中对应索引位置为5

中间位置元素'8',大于末尾元素'3',即'8'在上半区
起点'4',中点'8',target为9,不满足'起点 <= 目标 <= 中点'
取右
中间位置元素'0',小于末尾元素'3',即'0'在下半区
中点'0',结尾'3',target为9,不满足'中点 <= 目标 <= 结尾'
取左
<若算法每次循环未进行判断,即没有代码中的三个if判断>
中间位置元素'9',小于末尾元素'1',即'9'在上半区
起点'8',中点'9',target为9,满足'起点 <= 目标 <= 中点'
取左
头尾元素分别与target比较
<若算法每次循环进行了判断,即有代码中的三个if判断>
中间位置元素'9',target为9
RSA '4, 5, 6, 7, 8, 9, 0, 1, 2, 3', target = 9
抛掉'4, 5, 6, 7, 8'
缩小区间至'8, 9, 0, 1, 2, 3'
缩小区间至'8, 9, 0, 1'
抛掉'2, 3'
缩小区间至'8, 9'
抛掉'0, 1'
找到目标元素'9',下标为5

3.2 时间复杂度

  算法的时间复杂度为O(log n)。

3.3 空间复杂度

  算法的空间复杂度为O(1)。

4 源码

  注意事项:

  1. 如果每次循环都进行目标值判断,可以在中途找到目标元素的时候直接返回,即可得到结果,不用等待二分搜索结束;
  2. 如果每次循环不进行目标值判断,则需要在内层的条件判断上考虑边界;
  3. 返回的是序号,不是元素值。

  C++版本:

/**
* @param A: RSA数组
* @param target: 需要查找的目标值
* @return: 目标值的索引
*/
int search(vector<int> &A, int target) {
        // write your code here
        if (A.empty())
        {
            return -1;
        }

        int start = 0;
        int end = A.size() - 1;
        int mid = 0;
        while (start + 1 < end)
        {
            mid = start + (end - start) / 2;
            // 这里三个if是每次循环进行判断,为了加速搜索,如果当前已经找到目标值,直接返回,不用等到二分搜索结束
            if (A.at(start) == target)
            {
                return start;
            }
            if (A.at(mid) == target)
            {
                return mid;
            }
            if (A.at(end) == target)
            {
                return end;
            }

            if (A.at(mid) > A.at(end)) // 判断中点是否在上半区间
            {
                // 如果没有上面的三个if,此处的“<”、“>”都应该考虑边界,变为“<=”、“>=”
                if (A.at(start) < target && A.at(mid) > target) 
                {
                    end = mid;
                }
                else
                {
                    start = mid;
                }
            }
            if (A.at(mid) < A.at(end)) // 判断中点是否在下半区间
            {
                // 如果没有上面的三个if,此处的“<”、“>”都应该考虑边界,变为“<=”、“>=”
                if (A.at(mid) < target && A.at(end) > target)
                {
                    start = mid;
                }
                else
                {
                    end = mid;
                }
            }
        }

        if (A.at(start) == target)
        {
            return start;
        }
        if (A.at(end) == target)
        {
            return end;
        }

        return -1;
    }

  1. 寻找旋转排序数组中的最小值:https://blog.csdn.net/SeeDoubleU/article/details/118447152 ↩︎

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值