跳转至

81. 搜索旋转排序数组 II

难度:中等

题目

已知存在一个按非降序排列的整数数组 nums ,数组中的值不必互不相同。

在传递给函数之前,nums 在预先未知的某个下标 k0 <= 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

提示:

  • 1 <= nums.length <= 5000
  • -10^4 <= nums[i] <= 10^4
  • 题目数据保证 nums 在预先未知的某个下标上进行了旋转
  • -10^4 <= target <= 10^4

进阶:

此题与 搜索旋转排序数组 相似,但本题中的 nums 可能包含 重复 元素。这会影响到程序的时间复杂度吗?会有怎样的影响,为什么?

Reference

题解

首先,假定对于给定区间[left:right],如果它是有序的,则可以用二分查找的方式进行搜索。注意右端点是开区间,即right不在搜索范围内。

  1. 计算区间中点med_idx,将区间划分为[left:med_idx][med_idx:right]
  2. 如果target >= nums[med_idx],说明目标值在右半区间,将左端点left收缩到med_idx。否则,将右端点right收缩到med_idx
  3. 重复步骤1和2,直到区间长度等于1。此时nums[left]就是查找到的目标值,满足nums[left] <= target < nums[right]
  4. 判断比较targetnums[left]是否相等。

对于旋转数组,首先需要确定数组的哪一部分是有序的。对于搜索旋转排序数组题目来说,情况更为简单,因为数组中不包含重复数值。我们只需要取区间中点,判断nums[left] < nums[med_idx]nums[med_idx] < nums[right - 1]中的哪一个成立。如果前者成立,说明左半区间有序,否则右半区间有序。类似地,可以按照二分的方式进行搜索:

  1. 判断目标值是否落在有序区间内,如果是,则对有序区间进行二分查找,返回查找结果。
  2. 如果目标值不在有序区间内,将搜索区间缩小到另一半。
  3. 重复步骤1和2,直到搜索区间长度等于1。此时nums[left]就是查找到的目标值,满足nums[left] <= target < nums[right]
  4. 判断比较targetnums[left]是否相等。

对于搜索旋转排序数组 II题目,数组中可能包含重复数值,使得原本对于有序区间的判断变得复杂。

  1. 如果nums[left] <= nums[med_idx]nums[med_idx] <= nums[right - 1]两者中只满足一个,说明有序区间是满足条件的那一半。
  2. 如果两者都满足,说明nums[left] == nums[right - 1] == nums[med_idx],此时可以从一个方向对区间进行收缩,直到nums[left] != nums[right - 1],或者区间长度为1。
  3. 继续按照二分查找的方式进行搜索。
  4. 最坏情况下的时间复杂度为\(O(n)\),即数组中只有一个不同的数值。
class Solution:
    def bisect(self, target: int, nums: List[int], left: int = 0, right: Optional[int] = None):
        if right is None:
            right = len(nums)
        while right - left > 1:
            med_idx = (left + right) // 2
            if target >= nums[med_idx]:
                left = med_idx
            if target < nums[med_idx]:
                right = med_idx
        return target == nums[left]

    def search(self, nums: List[int], target: int) -> bool:
        left, right = 0, len(nums)
        while right - left > 1 and nums[left] == nums[right - 1]:
            right -= 1
        while right - left > 1:
            med_idx = (left + right) // 2
            if nums[med_idx] > nums[right - 1]:
                if nums[left] <= target and target <= nums[med_idx - 1]:
                    return self.bisect(target, nums, left, med_idx)
                left = med_idx
            else:
                if nums[med_idx] <= target and target <= nums[right - 1]:
                    return self.bisect(target, nums, med_idx, right)
                right = med_idx
        return target == nums[left]

如果需要查找位置,而不是简单判断元素是否存在,可以将返回值改为如下:

return left if target == nums[left] else -1

评论