[python 刷题] 153 Find Minimum in Rotated Sorted Array
题目:
Suppose an array of length
n
sorted in ascending order is rotated between1
andn
times. For example, the arraynums = [0,1,2,4,5,6,7]
might become:
[4,5,6,7,0,1,2]
if it was rotated4
times.[0,1,2,4,5,6,7]
if it was rotated7
times.Notice that rotating an array
[a[0], a[1], a[2], ..., a[n-1]]
1 time results in the array[a[n-1], a[0], a[1], a[2], ..., a[n-2]]
.Given the sorted rotated array
nums
of unique elements, return the minimum element of this array.You must write an algorithm that runs in
O(log n) time
.
一般来说,O(log n) time
是一个比较好的二分算法指向,这道题也是一个二分算法的经典题
这里假设没有被操作过的数组是排好序,每次进行旋转,都会将 list[-1]
移到 list[0]
,如题目中给的案例 [0,1,2,4,5,6,7]
,旋转过程为:
-
r = 1
[7,0,1,2,4,5,6]
-
r = 2
重复该过程:
[6,7,0,1,2,4,5]
-
r = 3
[5,6,7,0,1,2,4]
-
r = 4
如题目所示:
[4,5,6,7,0,1,2]
-
…
题目给的 input 是已经旋转好的数组,如:nums = [3,4,5,1,2]
,让找到数组中最小的数字,也就是找到未被旋转的数组中的 l[0]
可以注意到,旋转后的数组还是有规律的:
-
如果完成了
n
次旋转,即 n = l e n ( n u m s ) n = len(nums) n=len(nums),那么数组其实就是还原成排好序的数组,这个时候的规律为:l [ 0 ] < l [ 1 ] < . . . < l [ n − 1 ] l[0] < l[1] < ... < l[n-1] l[0]<l[1]<...<l[n−1]
换言之, l [ 0 ] l[0] l[0] 就是最小的数字
-
每次旋转都是将数组尾部的数字忘数组头部移动,因此,可以总结出:
数组中存在一个下标 p p p,满足条件 l [ 0 ] < l [ 1 ] < l [ 2 ] < . . . < l [ p ] l[0] < l[1] < l[2] < ... < l[p] l[0]<l[1]<l[2]<...<l[p],且满足 l [ p ] > l [ p + 1 ] l[p] > l[p+1] l[p]>l[p+1], l [ p + 1 ] < l [ p + 2 ] < . . . < l [ n − 1 ] l[p+1] < l[p+2] < ... < l[n-1] l[p+1]<l[p+2]<...<l[n−1] 和 l [ n − 1 ] < l [ 0 ] l[n-1] < l[0] l[n−1]<l[0] 这个条件
也就是说该数组中存在两个递增数组 l 1 ′ l'_1 l1′ 和 l 2 ′ l'_2 l2′, l 1 ′ l'_1 l1′ 的最后一个数字比 l 2 ′ l'_2 l2′ 的第最后一个数字大,而 l 2 ′ l'_2 l2′ 的第一个数字就是整个数组的最小值
以案例 nums = [3,4,5,1,2]
来说,只要找到一个点
p
p
p,满足
l
[
p
+
1
]
<
2
<
l
[
p
]
l[p+1] < 2 < l[p]
l[p+1]<2<l[p] 即可。因为
l
1
′
l'_1
l1′ 和
l
2
′
l'_2
l2′ 都是递增数组,所以这道题可以用二分算法:
- l 1 ′ l'_1 l1′ 中的值一定比 l 2 ′ l'_2 l2′ 的最后一个数字大
- l 2 ′ l'_2 l2′ 中的值一定小于等于 l 2 ′ l'_2 l2′ 的最后一个数字
class Solution:
def findMin(self, nums: List[int]) -> int:
# if the array has been rotated without restore
# nums[-1] will be smaller than n[0]
# if n[-1] >= n[0], then it's in sorted
# some edge cases:
if nums[-1] >= nums[0]:
return nums[0]
if len(nums) == 2:
return min(nums)
if nums[-1] < nums[-2]:
return nums[-1]
# find the position p, where n[p] < n[-1] < n[p - 1]
l, r = 0, len(nums) - 1
target = nums[-1]
while l <= r:
m = (l + r) // 2
if target < nums[m - 1] and target > nums[m]:
return nums[m]
if target < nums[m]:
l = m + 1
else:
r = m - 1
return -1
这个写法能过,并且我个人觉得虽然因为为了找最小数字手动 cover 了一些 edge cases 乱一些,不过理清思路什么的方便一些,个人留着用来推出干净写法
下面是找的别人的干净写法:
class Solution:
def findMin(self, nums: List[int]) -> int:
start , end = 0, len(nums) - 1
curr_min = float("inf")
while start < end :
mid = (start + end ) // 2
curr_min = min(curr_min,nums[mid])
# right has the min
if nums[mid] > nums[end]:
start = mid + 1
# left has the min
else:
end = mid - 1
return min(curr_min,nums[start])
这里就只是找 min
,不用特别在意一些 edge case