https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/
朴素遍历法
class Solution(object):
def minArray(self, numbers):
"""
:type numbers: List[int]
:rtype: int
"""
# 设最小值所处位置为i,数组中最小值的特性是:numbers[i-1] > numbers[i]
# 而数组中处于其他位置j的数字都满足 numbers[j-1] <= numbers[j]
n = len(numbers)
for i in range(n):
if numbers[i - 1] > numbers[i]:
return numbers[i]
# 所有元素都相等
return numbers[0]
时间复杂度:O(n)
空间复杂度:O(1)
二分查找法
使用二分查找缩小搜索范围,降低时间复杂度
下面的解法是错误的
class Solution(object):
def minArray(self, numbers):
"""
:type numbers: List[int]
:rtype: int
"""
# 设最小值所处位置为i,数组中最小值的特性是:numbers[i-1] > numbers[i]
# 而数组中处于其他位置j的数字都满足 numbers[j-1] < numbers[j]
# 使用二分查找找到这个位置
# 如何缩小搜索范围:
# 若numbers[0] > numbers[j],说明最小值的位置在j左侧
# 若numbers[0] < numbers[j],说明最小值的位置在j右侧
# 若numbers[0] = numbers[j],说明numbers[0,j]的值都相同,则最小值的位置在j右侧
left = 0
right = len(numbers) - 1
while left <= right:
print(left, right)
mid = (left + right) // 2
# 当mid=0时,numbers[mid-1]表示numbers最后一个数字
if numbers[mid] < numbers[mid - 1]:
return numbers[mid]
if numbers[left] <= numbers[mid]:
left = mid + 1
else:
right = mid - 1
# 所有元素都相等
return numbers[right]
错误的原因是:不能与数组中第一个元素比较来缩小查找范围
因为当numbers[left] < numbers[mid]
时,由于numbers[left]可能是最小值,因此不能直接舍弃掉mid左半边。这个问题导致了无法通过一次比较舍弃掉数组的一半
而与数组中最后一个元素比较则能很好地将数组分成两部分。
当numbers[mid] < numbers[right]
时,说明最小值在mid的左侧,且最小值有可能是numbers[mid],但不可能是numbers[-1],因此可以将numbers[mid+1:]这部分舍弃
当numbers[mid] > numbers[right]
时,说明最小值在mid的右侧,且最小值有可能是numbers[-1],但不可能是numbers[mid],因此可以将numbers[:mid+1]这部分舍弃
相等的情况在上面的解法中也没有考虑得很全面,下面我们着重分析
当numbers[mid] == numbers[right]
时,最小值可能在mid的右侧,即numbers[mid:]
先递增后递减;也可能在mid的左侧,即numbers[mid:]
的值全部相等。那么这怎么办?
根据官方题解:
我们唯一可以知道的是,由于它们的值相同,所以无论 numbers[-1] 是不是最小值,都有一个它的「替代品」numbers[mid] ,因此我们可以忽略二分查找区间的右端点。
由以上思路可得到正确的二分查找写法:
class Solution(object):
def minArray(self, numbers):
"""
:type numbers: List[int]
:rtype: int
"""
left = 0
right = len(numbers) - 1
while left <= right:
mid = (left + right) // 2
# 当mid=0时,numbers[mid-1]表示numbers最后一个数字
if numbers[mid - 1] > numbers[mid]:
return numbers[mid]
if numbers[mid] < numbers[right]:
# 前一个判断已经排除了最小值是numbers[mid]的可能
right = mid - 1
elif numbers[mid] > numbers[right]:
left = mid + 1
else:
# 相等时,忽略右端点
right -= 1
# 所有元素都相等
return numbers[0]
时间复杂度:O(logn)
空间复杂度:O(1)
优化
考虑想办法省去numbers[mid - 1] > numbers[mid]
这个判断
最终,搜索区间收敛到left = right - 1
,则mid = left
,left左边是一个递增序列,right右边也是一个递增序列
当numbers[mid] < numbers[right]
时,即numbers[left] < numbers[right]
,又因为left左侧序列递增,则必然有numbers[left-1] > numbers[left]
,因此此时结束循环返回numbers[left]
即可
当numbers[mid] > numbers[right]
时,很明显,numbers[right]
就是要找的最小值
当numbers[mid] == numbers[right]
时,返回任一即可
最终优化的代码为
class Solution(object):
def minArray(self, numbers):
"""
:type numbers: List[int]
:rtype: int
"""
left = 0
right = len(numbers) - 1
while left < right:
mid = (left + right) // 2
if numbers[mid] < numbers[right]:
# 此时mid有可能是最小值,应加入搜索区间
right = mid
elif numbers[mid] > numbers[right]:
left = mid + 1
else:
# 相等时,忽略右端点
right -= 1
# 所有元素都相等
return numbers[left]