寻找旋转排序数组中的最小值

标签: 数组 二分查找

难度: Medium

已知一个长度为 n 的数组,预先按照升序排列,经由 1n旋转 后,得到输入数组。例如,原数组 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] ,旋转 3 次得到输入数组。

示例 3:

输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。

提示:

  • n == nums.length
  • 1 <= n <= 5000
  • -5000 <= nums[i] <= 5000
  • nums 中的所有整数 互不相同
  • nums 原来是一个升序排序的数组,并进行了 1n 次旋转

Submission

运行时间: 32 ms

内存: 15 MB

class Solution:
    def findMin(self, nums: List[int]) -> int:
        left = 0
        right = len(nums) - 1
        while left < right:
            mid = (left + right) >> 1
            if nums[right] > nums[mid]:
                right = mid
            else:
                left = mid + 1
        return nums[left]

Explain

题解采用二分查找的方法寻找旋转排序数组中的最小值。该方法的核心思想是利用数组的部分有序性进行高效查找。在每次迭代中,计算中间位置 mid,并比较 mid 和右边界 right 的元素。如果 nums[mid] 小于 nums[right],则最小元素必定在左半边(包括 mid),因此将右边界移动到 mid。否则,如果 nums[mid] 大于等于 nums[right],则最小元素在右半边(不包括 mid),因此将左边界移动到 mid + 1。这个过程重复执行,直到左边界等于右边界,此时 left 或 right 指向的位置即为最小值。

时间复杂度: O(log n)

空间复杂度: O(1)

class Solution:
    def findMin(self, nums: List[int]) -> int:
        left = 0  # 初始化左边界
        right = len(nums) - 1  # 初始化右边界
        while left < right:  # 当左边界不等于右边界时循环
            mid = (left + right) >> 1  # 计算中点位置
            if nums[right] > nums[mid]:  # 如果右边界的值大于中点的值
                right = mid  # 移动右边界到中点
            else:
                left = mid + 1  # 否则移动左边界到中点的右侧
        return nums[left]  # 最终左边界即为最小值的位置

Explore

在旋转排序数组中,比较`nums[mid]`和`nums[right]`可以直接判断最小值是在左边还是右边。由于右边界`right`是数组的最后一个元素,如果`nums[mid]`小于`nums[right]`,说明从`mid`到`right`的区间是有序的,且`mid`可能是最小值,因此需要包含`mid`继续搜索左半区间。如果`nums[mid]`大于`nums[right]`,说明最小值在`mid`右侧的区间,因此搜索区间变为`mid+1`到`right`。使用`nums[left]`和`nums[mid]`的比较则无法直接确定无序区间和最小值的位置,因此不如`nums[mid]`和`nums[right]`的比较直观有效。

在本题的题解中,确实没有明确处理`nums[mid]`等于`nums[right]`的情况,因为题目假设数组中不存在重复元素。如果数组中有重复元素,这种情况会更复杂。通常的处理方法是将`right`指针减一,缩小查找范围,因为当`nums[mid]`等于`nums[right]`时,无法确定最小值是在左侧还是右侧,但可以确定`nums[right]`不是最小值,故可以安全地排除。

当`nums[right] > nums[mid]`时,意味着从`mid`到`right`这一区间内的元素是有序的,且没有旋转发生(即是升序)。由于`mid`位置的值小于`right`位置的值,`mid`可能就是这个区间的最小值,特别是在旋转点正好在`mid`或`mid`左侧时。因此,在搜索最小值时必须包括`mid`本身,以确保不错过最小值。