二分法05:搜索旋转排序数组

Posted 炫云云

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二分法05:搜索旋转排序数组相关的知识,希望对你有一定的参考价值。

搜索旋转排序数组

整数数组 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 。

示例 1:

输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4

示例 2:

输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1

以二分搜索为基本思路

在这里插入图片描述

可以发现的是,我们将数组从中间分开成左右两部分的时候,一定有一部分的数组是有序的。拿示例来看,我们从 6 这个位置分开以后数组变成了 [4, 5, 6,7] 和 [0, 1, 2] 两个部分,其中左边 [4, 5, 6,7] 这个部分的数组是有序的,其他也是如此。

这启示我们可以在常规二分查找的时候查看当前 mid 为分割位置分割出来的两个部分 [l, mid] 和 [mid + 1, r] 哪个部分是有序的,并根据有序的那个部分确定我们该如何改变二分查找的上下界,因为我们能够根据有序的那部分判断出 target 在不在这个部分:

简要来说:

nums[0] <= nums[mid] 时,[0, mid] 不包含旋转,即是有序数组 。

  • 如果nums[0] <= target <= nums[mid] 时 ,即target 在 [left, mid] 中,应该将右边界范围缩小至right = mid
  • 否则 target 在 [mid+1, right ] 中,应该将左边界范围缩小至left = mid +1

nums[mid] < nums[0] 时,[mid, -1] 不包含旋转,即是有序数组 。

  • 如果nums[mid] < target <= nums[-1] 时 ,即target 在 [mid+1, right ] 中,应该将左边界范围缩小至left = mid +1
  • 否则 target 在**[left, mid]** 中,应该将右边界范围缩小至right = mid
class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        if not nums:
            return -1
        left ,right = 0, len(nums) -1
        while left< right:
            mid = left+(right-left)//2
            if nums[0] <= nums[mid]: # 左边有序
                if  nums[0] <= target <= nums[mid]:
                    right = mid
                else:
                    left = mid +1
            else:                    # 右边有序
                if nums[mid] < target <= nums[-1]:
                    left = mid +1
                else:
                    right = mid

        if left==right and nums[left]==target:
            return left
        else:
            return -1

复杂度分析

时间复杂度: O ( log ⁡ n ) O(\\log n) O(logn),其中 n 为 nums \\textit{nums} nums 数组的大小。整个算法时间复杂度即为二分查找的时间复杂度 O ( log ⁡ n ) O(\\log n) O(logn)

空间复杂度: O ( 1 ) O(1) O(1) 。我们只需要常数级别的空间存放变量。

搜索旋转排序数组 II

这是 搜索旋转排序数组 的延伸题目,本题中的 nums 可能包含重复元素。

已知存在一个按非降序排列的整数数组 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,4,4,5,6,6,7] 在下标 5 处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4] 。

给你 旋转后 的数组 nums 和一个整数 target ,请你编写一个函数来判断给定的目标值是否存在于数组中。如果 nums 中存在这个目标值 target ,则返回 true ,否则返回 false 。

示例 1:

输入:nums = [2,5,6,0,0,1,2], target = 0
输出:true

示例 2:

输入:nums = [2,5,6,0,0,1,2], target = 3
输出:false

思路:二分查找

对于数组中有重复元素的情况,二分查找时可能会有 a [ l ] = a [ mid ] = a [ r ] a[l]=a[\\textit{mid}]=a[r] a[l]=a[mid]=a[r] ,此时无法判断区间 [ l , mid ] [l,\\textit{mid}] [l,mid]和区间 [ mid + 1 , r ] [\\textit{mid}+1,r] [mid+1,r] 哪个是有序的。

例如 nums = [ 3 , 1 , 2 , 3 , 3 , 3 , 3 ] \\textit{nums}=[3,1,2,3,3,3,3] nums=[3,1,2,3,3,3,3] target = 2 \\textit{target}=2 target=2,首次二分时无法判断区间 [ 0 , 3 ] [0,3] [0,3] 和区间 [ 4 , 6 ] [4,6] [4,6] 哪个是有序的。

对于这种情况,我们只能将当前二分区间的左边界加一,相当于去掉重复的干扰项,然后在新区间上继续二分查找。

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        if not nums:
            return False
        if len(nums)==1:
            return nums[0]==target
        left ,right = 0, len(nums) -1
        while left< right:
            mid = left+(right-left)//2
            if nums[left] == nums[mid] and nums[mid]== nums[right]:
                left +=1

            elif nums[left] <= nums[mid]: # 左边有序
                if  nums[left] <= target <= nums[mid]:
                    right = mid
                else:
                    left = mid +1
            else:                    # 右边有序
                if nums[mid] < target <= nums[-1]:
                    left = mid +1
                else:
                    right = mid

        if left==right and nums[left]==target:
            return True
        else:
            return False

复杂度分析

时间复杂度: O ( n ) O(n) O(n),其中 n 是数组 nums \\textit{nums} nums 的长度。最坏情况下数组元素均相等且不为 target \\textit{target} target,我们需要访问所有位置才能得出结果。

空间复杂度:O(1)。

参考

力扣(LeetCode) (leetcode-cn.com)]
《画解剑指 Offer 》

以上是关于二分法05:搜索旋转排序数组的主要内容,如果未能解决你的问题,请参考以下文章

搜索旋转排序数组[特殊二分]

java刷题--33搜索旋转排序数组

二分查找团灭力扣旋转排序数组系列

二分查找(通过相对位置判断区间位置)--17--二分--LeetCode33搜索旋转排序数组

排序旋转整数数组,搜索算法[重复]

62. 搜索旋转排序数组