81. 搜索旋转排序数组 II¶
难度:中等
题目¶
已知存在一个按非降序排列的整数数组 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
提示:
1 <= nums.length <= 5000
-10^4 <= nums[i] <= 10^4
- 题目数据保证
nums
在预先未知的某个下标上进行了旋转 -10^4 <= target <= 10^4
进阶:
此题与 搜索旋转排序数组 相似,但本题中的 nums
可能包含 重复 元素。这会影响到程序的时间复杂度吗?会有怎样的影响,为什么?
题解¶
首先,假定对于给定区间[left:right]
,如果它是有序的,则可以用二分查找的方式进行搜索。注意右端点是开区间,即right
不在搜索范围内。
- 计算区间中点
med_idx
,将区间划分为[left:med_idx]
和[med_idx:right]
。 - 如果
target >= nums[med_idx]
,说明目标值在右半区间,将左端点left
收缩到med_idx
。否则,将右端点right
收缩到med_idx
。 - 重复步骤1和2,直到区间长度等于1。此时
nums[left]
就是查找到的目标值,满足nums[left] <= target < nums[right]
。 - 判断比较
target
和nums[left]
是否相等。
对于旋转数组,首先需要确定数组的哪一部分是有序的。对于搜索旋转排序数组题目来说,情况更为简单,因为数组中不包含重复数值。我们只需要取区间中点,判断nums[left] < nums[med_idx]
和nums[med_idx] < nums[right - 1]
中的哪一个成立。如果前者成立,说明左半区间有序,否则右半区间有序。类似地,可以按照二分的方式进行搜索:
- 判断目标值是否落在有序区间内,如果是,则对有序区间进行二分查找,返回查找结果。
- 如果目标值不在有序区间内,将搜索区间缩小到另一半。
- 重复步骤1和2,直到搜索区间长度等于1。此时
nums[left]
就是查找到的目标值,满足nums[left] <= target < nums[right]
。 - 判断比较
target
和nums[left]
是否相等。
对于搜索旋转排序数组 II题目,数组中可能包含重复数值,使得原本对于有序区间的判断变得复杂。
- 如果
nums[left] <= nums[med_idx]
和nums[med_idx] <= nums[right - 1]
两者中只满足一个,说明有序区间是满足条件的那一半。 - 如果两者都满足,说明
nums[left] == nums[right - 1] == nums[med_idx]
,此时可以从一个方向对区间进行收缩,直到nums[left] != nums[right - 1]
,或者区间长度为1。 - 继续按照二分查找的方式进行搜索。
- 最坏情况下的时间复杂度为\(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