剑指 Offer 11. 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,
输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
示例 1:
输入:[3,4,5,1,2]
输出:1
示例 2:
输入:[2,2,2,0,1]
输出:0
解题思路
二分查找,注意当numbers[mid]==numbers[right]时无法判断mid是在左排序还是右排序中,所以解决方案是 right=right-1 缩小判断范围。对于 right=right-1,只需证明每次执行此操作后,旋转点 x 仍在 [left,right]区间内即可。(可以理解为:因为相等,所以删掉一个留下一个,我们在剩下区间搜索,仍可以搜索到删除的那个元素值。)
class Solution:
def minArray(self, numbers: List[int]) -> int:
left, right = 0, len(numbers) - 1
while left< right:
mid = (left + right) // 2
if numbers[mid]>numbers[right]:
left = mid + 1
elif numbers[mid]<numbers[right]:
right = mid
else:
right -= 1
return numbers[left]
面试题 10.03. 搜索旋转数组
搜索旋转数组。给定一个排序后的数组,包含n个整数,但这个数组已被旋转过很多次了,次数不详。
请编写代码找出数组中的某个元素,假设数组元素原先是按升序排列的。若有多个相同元素,
返回索引值最小的一个。
示例1:
输入: arr = [15, 16, 19, 20, 25, 1, 3, 4, 5, 7, 10, 14], target = 5
输出: 8(元素5在该数组中的索引)
示例2:
输入:arr = [15, 16, 19, 20, 25, 1, 3, 4, 5, 7, 10, 14], target = 11
输出:-1 (没有找到)
提示:
arr 长度范围在[1, 1000000]之间
解题思路
先使用二分查找找出数组最小值的索引,然后将其转为升序的数组,再使用二分查找目标值
注意如果数组的最后一个数和第一个数相同,则去掉最后一个数,保证数组最后一个数和第一个数不同
class Solution:
def search(self, arr: List[int], target: int) -> int:
# 如果数组的最后一个数和第一个数相同,则去掉最后一个数,保证数组最后一个数和第一个数不同
while len(arr) > 1 and arr[-1] == arr[0]:
arr.pop()
# 数组长度
length = len(arr)
# 先找出最小值的索引
left, right = 0, len(arr) - 1
while left<right:
mid = (left + right) // 2
if arr[mid]>arr[right]:
left = mid + 1
elif arr[mid]<arr[right]:
right = mid
else:
right -= 1
# left表示数组中最小值的索引
# 将数组中前面的升序部分拼接到数组后面,使得数组从left~right部分为递增序列
right = left + length - 1
# 如果target比数组最小的还要小,那么肯定不存在
if target<arr[left]: return -1
# 二分查找target
while left<right:
mid = (left + right) >> 1
# mid%length表示在原数组的位置,因为实际原数组并没有改变,只是将其索引改变了
# 由于当有多个相同元素时需要返回索引最小的,所以当arr[mid%length]>=target时只能取right = mid
# 直到left==right截止(向下取整)
if arr[mid%length]>=target:
right = mid
else:
left = mid + 1
if arr[left%length]==target:
return left%length
else:
return -1