M153.寻找旋转排序数组中的最小值
binary search, https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/
已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
- 若旋转
4次,则可以得到[4,5,6,7,0,1,2] - 若旋转
7次,则可以得到[0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。
给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。
示例 1:
输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。示例 2:
输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。示例 3:
输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。提示:
n == nums.length1 <= n <= 5000-5000 <= nums[i] <= 5000nums中的所有整数 互不相同nums原来是一个升序排序的数组,并进行了1至n次旋转
这道题要求在
核心思路
在旋转排序数组中,数组被分成了两个升序的部分:
- 左侧部分的所有元素都大于右侧部分的所有元素(如果发生了旋转)。
- 我们要寻找的最小值,正是右侧部分的第一个元素。
我们可以通过比较中间元素 nums[mid] 和 右边界元素 nums[right] 来确定最小值所在的区间:
如果
nums[mid] < nums[right]:- 说明从
mid到right这一段是升序的。 - 最小值可能是
nums[mid]本身,也可能在mid的左侧。 - 因此,我们将右边界收缩:
right = mid。
- 说明从
如果
nums[mid] > nums[right]:- 说明从
mid到right之间发生了“断层”(旋转点在其中)。 - 最小值一定在
mid的右侧。 - 因此,我们将左边界收缩:
left = mid + 1。
- 说明从
循环结束条件:
- 当
left == right时,我们就找到了数组的最小值。
- 当
代码实现
from typing import List
class Solution:
def findMin(self, nums: List[int]) -> int:
left, right = 0, len(nums) - 1
# 二分查找
while left < right:
mid = left + (right - left) // 2
# 如果中间值小于右边界值,说明右半部分是递增的
# 最小值可能是 mid,或者在 mid 的左边
if nums[mid] < nums[right]:
right = mid
# 如果中间值大于右边界值,说明最小值一定在 mid 的右边
else:
left = mid + 1
# 最终 left == right,指向的就是最小值
return nums[left]复杂度分析
- 时间复杂度:
。每次循环都将搜索区间缩小一半。 - 空间复杂度:
。只使用了常数级别的额外空间。
为什么不和 nums[left] 比较?
如果数组没有旋转(即它是完全升序的,如 [1, 2, 3]),nums[mid] 会大于 nums[left]。但在旋转数组(如 [4, 5, 1, 2, 3])中,nums[mid] 同样可能大于 nums[left]。 这会导致逻辑判断变得复杂。而比较 nums[mid] 和 nums[right] 可以非常清晰地判断 mid 是落在“大的左半段”还是“小的右半段”,逻辑更加统一。