整数数组 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 。
你必须设计一个时间复杂度为 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
示例 3:
输入: nums = 1, target = 0
输出:-1
提示:
1 <= nums.length <= 5000-104 <= nums[i] <= 104nums中的每个值都 独一无二- 题目数据保证
nums在预先未知的某个下标上进行了旋转 -104 <= target <= 104
由于是旋转数组,所以具备以下性质:
任意取 nums 中的某个数 x 与 nums[n - 1] 比大小:
- 如果 x >
nums[n - 1],说明:nums 有两个递增段,且第一个递增段的所有元素均大于第二个递增段的所有元素,且 x 在第一个递增段 - 如果 x ≤
nums[n - 1],那么 x 在第二个递增段,或者 nums 本身是递增数组,只有一段
设 x = nums[mid] 是我们要二分的数,要判断 x 和 target 的位置关系,将 x 和 target 这两个数与 nums[n - 1] 比大小:
- 如果 target >
nums[n - 1]≥ x,则 target 在第一段,x 在第二段,target 在 x 左侧 - 如果 x >
nums[n - 1]≥ target,则 x 在第一段,target 在第二段,target 在 x 右侧 - 否则,x 和 target 在同一段,直接比较 x 与 target 的大小即可知谁在左边、谁在右边
知道了谁在左、谁在右,就能进行二分了
python
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = -1, len(nums) - 1
while left + 1 < right:
mid = (left + right) // 2
x = nums[mid]
if target > nums[-1] >= x:
# target 在第一段,x 在第二段
right = mid
elif x > nums[-1] >= target:
# target 在第二段,x 在第一段
left = mid
elif x >= target:
# x 和 target 在同一段且 x 在 target 右侧
right = mid
else:
left = mid
return right if nums[right] == target else -1
什么时候是 left < right,什么时候是 left <= right,什么时候是 left + 1 < right?
做二分,搞清楚三个问题:
1.维护的区间是什么?
2.mid要不要保留?
3.区间是不是真的缩小了?
如果这题是 left 初始为 0 ,while 循环的条件是 left < right,那么可能会出现死循环,例如 left = 3,right = 4,那么 mid = 3,然后如果走了第二个分支,那么 mid 会赋值 给 left,那区间就没有缩小,就死循环了
那能不能 left = mid + 1?可以,但是不能同时 right = mid - 1,即 elif nums[mid] >= target 分支中不能写成 right = mid - 1,但 if target > nums[-1] >= nums[mid] 分支中就可以写 right = mid - 1,因为 elif 分支相当于 nums[mid] == target or nums[mid] > target,即 nums[mid] 是有可能成为答案的,如果你 right = mid - 1,那就把答案排除在区间之外了
所以也可以:
python
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1
while left < right:
mid = (left + right) // 2
x = nums[mid]
if target > nums[-1] >= x:
# target 在第一段,x 在第二段
right = mid - 1
elif x > nums[-1] >= target:
# target 在第二段,x 在第一段
left = mid + 1
elif x >= target:
# x 和 target 在同一段且 x 在 target 右侧
right = mid
else:
left = mid + 1
return right if nums[right] == target else -1
处理输入输出版:
cases.txt:
[4,5,6,7,0,1,2]
0
[4,5,6,7,0,1,2]
3
[1]
0
Python
python
from typing import List
import ast
def search(nums: List[int], target: int) -> int:
left, right = -1, len(nums) - 1
while left + 1 < right:
mid = (left + right) // 2
x = nums[mid]
if target > nums[-1] >= x:
# target 在第一段,x 在第二段
right = mid
elif x > nums[-1] >= target:
# x 在第一段,target 在第二段
left = mid
elif x >= target:
# x 与 target 在同一段且 x 在 target 右侧
right = mid
else:
# x 与 target 在同一段且 x 在 target 左侧
left = mid
return right if nums[right] == target else -1
def read_cases(filename: str):
cases = []
with open(filename, "r") as f:
lines = [
line.strip() for line in f
]
for i in range(0, len(lines), 2):
nums = ast.literal_eval(
lines[i]
)
target = ast.literal_eval(
lines[i + 1]
)
cases.append(
(nums, target)
)
return cases
def main():
cases = read_cases("cases.txt")
for nums, target in cases:
res = search(nums, target)
print(f"nums = {nums}")
print(f"target = {target}")
print(f"result = {res}")
print()
if __name__ == "__main__":
main()