LeetCode153 寻找旋转排序数组中的最小值(带扩展)

75 阅读3分钟

leetcode.cn/problems/fi…

image.png

解法一:变种二分查找

每次把 nums[mid] 和数组最后一个数 nums[len-1]比较,

  • 如果 nums[mid]>nums[n−1],那么可以推论:
    • nums 一定被分成左右两个递增段;
    • 第一段的所有元素均大于第二段的所有元素;
    • nums[mid] 一定在第一段。
    • 最小值在第二段。
    • 所以最小值一定在 nums[mid]的右边,需要更新left,到右边继续找最小值
  • 如果 nums[mid] <= nums[n−1],那么可以推论:
    • nums[mid]一定在第二段(也可能数组没被旋转,那么就是全局递增数组,最小值就是第一个元素)
    • nums[mid]要么是最小值,要么在最小值右边,需要更新right,到左边继续寻找最小值。

由于每次是和最后一个元素比较,因此二分搜索范围只需要在闭区间【0, len(nums)-2】,避免最小值在最后一个数时,循环退出条件left = right +1,导致数组越界的情况

func findMin(nums []int) int {
    left, rignt := 0, len(nums)-2
    for left <= rignt{
        mid := left + (rignt - left)/2
        if nums[mid] > nums[len(nums)-1] {
            left = mid + 1
        }else {
            rignt = mid - 1
        }
    }
    return nums[left]
}

换成开区间写法也可以

func findMin(nums []int) int {
    left, right := 0, len(nums) - 1
    for left < right {
        pivot := left + (right - left) / 2
        if nums[pivot] < nums[right] {
            right = pivot
        } else {
            left = pivot + 1
        }
    }
    return nums[left]
}

扩展:寻找旋转排序数组中的中位数

其实上面的解法,找到的最小值即为旋转排序数组的旋转点,把原数组分为左右两部分递增子数组,现在问题变成在两个有序递增数组中,找到中位数。

根据中位数的定义,当 m+n 是奇数时,中位数是两个有序数组中的第 (m+n)/2 个元素,当 m+n 是偶数时,中位数是两个有序数组中的第 (m+n)/2个元素和第 (m+n)/2+1个元素的平均值。因此,这道题可以转化成寻找两个有序数组中的第 k 小的数,其中 k 为 (m+n)/2 或 (m+n)/2+1

// findPivot 用于找到旋转排序数组的旋转点
func findPivot(nums []int) int {
    left, right := 0, len(nums)-1
    for left < right {
        mid := left + (right-left)/2
        if nums[mid] > nums[right] {
            left = mid + 1
        } else {
            right = mid
        }
    }
    return left
}

// findMedian 用于找到旋转排序数组的中位数
func findMedian(nums []int) float64 {
    n := len(nums)
    pivot := findPivot(nums)
    leftArr := nums[pivot:] // 数值较小的半边
    rightArr := nums[:pivot] // 数值较大的半边

    // 双指针法寻找中位数
    i, j := 0, 0
    var prev, current int

    for k := 0; k <= n/2; k++ {
        prev = current
        if i < len(leftArr) && (j >= len(rightArr) || leftArr[i] < rightArr[j]) {
            // `i < len(leftArr)` 确保 `i` 指针没有越界,即 `leftArr` 中还有元素未被遍历
            // `j >= len(rightArr)` 表示 `rightArr` 中的元素已经全部遍历完
            // `leftArr[i]` 是当前两个数组中较小的元素
            current = leftArr[i]
            i++
        } else {
            // `rightArr[j]` 是当前两个数组中较小的元素
            current = rightArr[j]
            j++
        }
    }

    if n%2 == 1 {
        return float64(current)
    }
    return float64(prev+current) / 2
}