搜索旋转数组是面试中很容易考到的题目,我面试次数不多,都已经碰到两次,一次在头条,一次在作业帮。在LeetCode中出现了四道关于搜索旋转数组的题目。
- LeetCode 33 题:搜索旋转排序数组
- LeetCode 81 题:搜索旋转排序数组-ii
- LeetCode 153 题:寻找旋转排序数组中的最小值
- LeetCode 154 题:寻找旋转排序数组中的最小值-ii
搜索旋转数组 就是 对数组进行二分法的灵活运用。
33和81寻找是否存在某值,33数组里面元素不重复,81里面可重复。
153和154寻找最小值,153数组元素不重复,154数组元素可重复。
一、原型:
首先上二分查找的原型题目:LeetCode 704 题 :二分查找
def search(nums, target):
l = 0
r = len(nums) - 1
while l <= r:
mid = (l + r) // 2
if nums[mid] == target:
return mid
if nums[mid] < target:
l = mid + 1
else:
r = mid - 1
return -1
二、搜索旋转排序数组
看完原型然后就看33题:
题目:假设按照升序排序的数组在预先未知的某个点上进行了旋转。 ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。 搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。 你可以假设数组中不存在重复的元素。 你的算法时间复杂度必须是 O(log n) 级别。
了解了什么是旋转数组,我们可以通过中点和两端的相比较得知,那边是有序的,那边是有旋转点的。正常来说,一个升序数组最左边nums[left] 是要小于nums[mid], 如果出现了nums[left] > nums[mid] ,就可以说明左一半出现旋转点,右一半是有序的。我们只需要做到两步:找到有序的一半,判断target是否在找到有序的一半。
def search(nums, target):
l = 0
r = len(nums) - 1
while l <= r:
mid = (l + r)//2
if nums[mid] == target:
return mid # 目前为止和 原型一样的代码
if nums[l] <= nums[mid]: # Step 1、找到有序的一半:左边有序,易错1:因为mid 可能 = l,所以不能去掉 =
if nums[l] <= target <= nums[mid]: # Step 2、判断target是否在有序的一半
r = mid - 1
else:
l = mid + 1
else: # 右边有序
if nums[mid] <= target <= nums[r]:
l = mid + 1
else:
r = mid - 1
return -1
81. 搜索旋转排序数组 II :如果存在多个重复元素,就会出现一些case: [1, 3, 1, 1, 1]
我们在33题中通过对nums[left] 和 nums[mid] 做大小比较来找到有序的一半, 但是如果nums[left] = nums[mid]= nums[right]就没法利用这个对比了。
所以我们加多了了一个if分支来处理这种情况:左指针不断往右移,相当于遍历数组了。
if nums[mid] == nums[left]:
left += 1
完整代码如下:
def search(nums, target):
left = 0
right = len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return True
if nums[mid] == nums[left]: # 和33的不同点
left += 1
elif nums[mid] > nums[left]:
if nums[mid] > target >= nums[left]:
right = mid - 1
else:
left = mid + 1
else:
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return False
三、寻找旋转排序数组中的最小值
153和154题:正常来说,升序数组中最小值就是nums[left], 现在是旋转数组,所以是要找到旋转数组的旋转的那一部分的第一个元素
这两道题和二的两道题有下面的两点不同:
- 上面两道题在二分时候都是mid-1 ,mid+1,把mid排除在外,在下面这两道题中会把mid也考虑在里面
- 如果是nums[right] 和 nums[mid] 做比较,那么不用处理;
如果是nums[left] 和 nums[mid] 作比较,那么要加多一个初试判断:
if nums[l] < nums[r]: # 如果已经二分到全部是有序的了,就返回第一个
return nums[l]
至于为什么会这样,可以试试用一个有序数组[1,3,5] 手动试试 - 33和81:查看是否存在某元素,即有可能不存在,所以要用while l <= r
153和154: 找最小值,一定存在。所以用while l < r,不用加=
下面给出两道题代码:
153:
(1)nums[left] 和 nums[mid] 作比较
def findMin(nums):
l = 0
r = len(nums) - 1
while l < r:
mid = (l + r) // 2
if nums[l] < nums[r]:
return nums[l]
if nums[l] > nums[mid]:
r = mid
else:
l = mid+1
return nums[l]
(2)nums[right] 和 nums[mid]作比较
def findMin(nums):
l = 0
r = len(nums) - 1
while l < r:
mid = (l + r) // 2
if nums[r] < nums[mid]: # 旋转数组第一个元素在右边,而且不包括mid
l = mid + 1
else: # nums[mid] <= nums[r]
r = mid # 旋转数组第一个元素在左边,包括mid
return nums[l]
154: 多了处理重复元素的分支
def findMin(nums):
l, r = 0, len(nums) - 1
while l < r:
mid = (l+ r) // 2
if nums[mid] > nums[r]: l = mid + 1
elif nums[mid] < nums[r]: r = mid
else: r = r - 1 # 多了处理重复元素的分支
return nums[l]