二分_剑指offer.11_旋转数组的最小数字

题目

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [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

分析思路

  • 如下图所示,寻找旋转数组的最小元素即为寻找 右排序数组 的首个元素 numbers[x] ,称 x 为 旋转点 。

  • 排序数组的查找问题首先考虑使用 二分法 解决,其可将遍历法的 线性级别 时间复杂度降低至 对数级别 。

在这里插入图片描述

方法:二分查找

算法流程

  1. 循环二分: 设置 i, j 指针分别指向 numbers 数组左右两端,m = (i + j) // 2为每次二分的中点( “//” 代表向下取整除法,因此恒有 i ≤ m < j),可分为以下三种情况:

    • 当numbers[m] > numbers[j] 时: m一在左排序数组中,即旋转点x一定在[m+ 1,j]闭区间内,因此执行i= m+ 1;
    • 当numbers[m] < numbers[j] 时: m一定在右排序数组中,即旋转点x一定在[i, m]闭区间内,因此执行j = m;
    • 当numbers[m] = numbers[j] 时:无法判断m在哪个排序数组中,即无法判断旋转点x在[i, m]还是[m+ 1,j]间中。解决方案:执行j= j- 1缩小判断范围(分析见以下内容) 。
  2. 返回值:当i = j时跳出二分循环,并返迥numbers[i]即可。

    **思考:**是否可以用numbers[m] 和numbers[i]|比较做代替?
    **解析:**不可以。因为做比较的目的是判断m在哪个排序数组中。在numbers [m]>numbers[i] 情况下,无法判断m在哪个排序数组中。本质是因为j初始值肯定在右排序数组中; i 初始值无法确定在哪个排序数组中。
    示例:当i=0,j= 4,m=2时,有numbers[m] > numbers[i] , 以下两示例
    得出不同结果。
    numbers= [1, 2,3,4,5]旋转点x= 0: m在右排序数组(际例只有右排序
    数组) ;
    numbers= [3, 4,5,1,2]旋转点x= 3: m在左排序数组。

    展开分析numbers[m] = numbers[j] 情况:

    1. **无法判定m在左(右)排序数组:**设以下两个旋转点值为 0的示例数组,则当i=0,j= 4时m=2,两际例结果不同。
      1. 例[1,0,1,1,1]:旋转点x= 1,因此m= 2在右排序数组中。
      2. 例[1,1,1,0,1]:旋转点x=3,因此m = 2在左排序数组中。
    2. j= j - 1操作的正确性证明:只需证明晦次执行此操作后,旋转点x仍在[i, j]区间内即可。
      1. 若m在右排序数组中: numbers[m] == numbers[j],因此数组[m,j] (恒有m < j)间内所有元素值相等,执行j= j- 1只会抛弃-个重复值,因此旋转点x仍在[i, j]区间内。
      2. **若m在左排序数组中:**于|左排序数组任一元素>=右排序数组任一元素|,因此可推出旋转元素值numbers[x] <= numbers[j] == numbers[m],则有:
        1. 若numbers[x] < numbers[j] :即j方仍有值更小的元素,执行j= j- 1后旋转点x 仍在[i, j]区间内。
        2. 若numbers[x] = numbers[j] :分为以下两种情况。
          1. 当j> x:易得执行j= j- 1后旋转点x仍在[i,j]区间内。
          2. 当j= x:特殊情况,即执行j=j- 1后旋转点x可能不在[i, j]区间内。例如
            [1,1,1,2,3,1],当i=0,m=2,j= 5时执行j= j- 1后虽然秩了旋转索引x= 5,但最终返回值仍正确(最终返回numbers[0] 等于旋转点值numbers[5]),这是因为:之后的二分循环一直在执行j = m,区间[i, m]内的元素值一定都等于旋转点值numbers[x] ( :区间内元素值既要满足≥也要满足< numbers[x] ),因此仍可保证正确的返回值。
    3. 总结:仿河以保证返回值numbers[i]| 等于旋转点值numbers[x] ;但在少数特例下i不是旋转
      点x。本题目职要求返回“旋转点的值”,因此本方法可行。
	public int minArray(int[] numbers) {
		  int left = 0, right = numbers.length - 1;
		  while(left < right) {
			  int mid = left + (right - left) / 2;
			  if(numbers[mid] < numbers[right]) {
				  right = mid;
			  }else if(numbers[mid] > numbers[right]) {
				  left = mid + 1;
			  }else {
				  right--;
			  }
		  }
		  return numbers[left];
	}

python

class Solution:
    def minArray(self, numbers: [int]) -> int:
        i, j = 0, len(numbers) - 1
        while i < j:
            m = (i + j) // 2
            if numbers[m] > numbers[j]: i = m + 1
            elif numbers[m] < numbers[j]: j = m
            else: j -= 1
        return numbers[i]

猜你喜欢

转载自blog.csdn.net/weixin_45333934/article/details/107575637
今日推荐