【剑指offer】旋转数组的最小数字(二分)

剑指offer-旋转数组的最小数字(二分)

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。

给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一次旋转,该数组的最小值为1。

示例 1:

输入:[3,4,5,1,2]
输出:1
示例 2:

输入:[2,2,2,0,1]
输出:0

解析:

  • 数组的分段有序的,可以联想到二分。
  • 二分的核心思想其实是缩短遍历的范围,利用数组分段有序的性质,减少遍历元素的数量
  • 我们要寻找最小值,其实就是寻找旋转点所在的位置,即为寻找 右排序数组 的首个元素。

算法流程:

  • 用两个指针l和r分别直接数组的首位两端,mid=(L+R)/2

  • 如果num[mid]<num[R],那么mid肯定是位于右排序数组的,旋转点肯定位于[L,mid]所在的闭区间内,所以可以缩小二分范围,令 R=mid

  • 如果num[mid]>num[R],那么mid肯定是位于左排序数组的,旋转点肯定位于[mid+1,R]闭区间内,所以可以缩小二分范围,令 L=mid+1

  • 如果num[mid]=num[R],那么我们无法确定mid位于哪个排序数组,通过 R=R-1 缩小二分范围

  • 当 L==R 时跳出二分,num[R]值即是旋转点值,即最小值

复杂度分析:

  • 时间复杂度:O(logN)
  • 空间复杂度:O(1),常数大小的空间

情况说明:

  • 那 num[mid]=num[R] 为什么可以通过 R=R-1 缩小范围呢?不怕丢失旋转点吗?
    • 证明:首先旋转点肯定是小于等于 R 的,等于r的情况,虽然丢失了旋转点,但是最终返回的元素num[R]肯定是等于旋转点值的,虽然下标不一定对得上,但题目只要是返回值即可
  • 为什么不用num[mid]和num[L]比较呢?
    • 如果使用num[mid]和num[L]比较,那么在某些情况下,无法判断mid位于左右哪个排序数组
      • 比如样例[1,2,3,4,5],num[mid]>num[L],mid位于右排序数组,此例只有右排序数组
      • 比如样例[3,4,5,1,2] num[mid]>num[L],mid位于左排序数组
    • 所以无法用num[mid]和num[L]做比较,根本原因在于 R 初始值肯定是在右排序数组,但L的初始值则不确定在哪个排序数组,比如上面两个例子。
func minArray(numbers []int) int {
	n := len(numbers)
	if n==0{
		return 0
	}
	l := 0
	r := n - 1

	for l< r {
		mid := (l + r) / 2
		// mid位于左排序数组
		if numbers[mid] > numbers[r] {
			l=mid+1
		} else if numbers[mid] < numbers[r] {
			// mid位于右排序数组
			r = mid
		}else {
			// 无法确定mid位于哪个排序数组
			r--
		}
	}
	return numbers[r]
}
posted @ 2019-09-22 17:09  西*风  阅读(120)  评论(0编辑  收藏  举报