「剑指Offer 11.旋转数组的最小数字」

100 阅读3分钟

「剑指Offer 11.旋转数组的最小数字」

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

题目描述(level 简单)

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。给你一个可能存在 重复 元素值的数组 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
思路分析

根据题意,就是将排序递增数组从开始的一段数字移动到并添加到数组尾部,排序且递增数组首先会想到使用二分法来解决,进一步转化,题目就是要求出数组的最小值并且时间与空间复杂度尽可能低。直接将数组排序然后返回nums[0]。顺利解决,不过这样写的风险怕是要回家等通知了🤪。二分法,不断缩小范围,目标就是为了找到最小值所在的区间。分别设i,j对应下标0, nums.length - 1。“中间值”为nums[mid],以为示例数组[3,4,5,1,2],考虑一般通用的情况,首次计算时i = 0,如果将nums[mid]的值与nums[i]作比较(nums[mid]>nums[0]),此时无法判断最小值存在于哪个区间内([i,mid]、[mid,j])。而旋转后的新数组可以看成两个增序数组的拼接(一般情况下,不考虑特例)。那么最小值应该是在中部附近,而j本身就存在于右侧区间内,与nums[j]比较是合理的。主要是处理逼近最小值时的区间边界处理,防止“错过”最小值。

  • 定义i,j,mid其中mid = (i + j) / 2将数组分成左右两个区间。
  • nums[mid]<nums[j]时那么最小值在左区间此时移动区间指向j = mid
  • nums[mid]>nums[j]时那么最小值在右区间,由于已经明确nums[mid]>nums[j]则可以直接从下一个数开始遍历i = mid + 1
  • nums[mid]=nums[j]时,也是关键的点,同nums[mid]>nums[j],此时需要移动j的指向,j = j -1,防止漏掉最小值。
  • 返回结果nums[i]
代码实现
class Solution {
  public int minArray(int[] nums) {
    if (null == nums || nums.length < 1) {
      return -1;
    }
    int i = 0, j = nums.length - 1, mid;
    while (i < j) {
      mid = (i + j) >>> 1 ; //i + (j - i) / 2
      if (nums[mid] < nums[j]) {
        j = mid;
      } else if (nums[mid] > nums[j]) {
        i = mid + 1;
      } else {
        //[1,1,1,1, 1 ,1,0,1,1]
        j = j - 1;
      }
    }
    return nums[i];
  }
 }
  • Tips

在求中间坐标时mid=(i+j)>>>1采用了位运算,其实跟mid=i+(j-i)/2 的效果是一样的,目的是为了防止int溢出,但是如果写成mid=i+(j-i)>>>1就重复了,会存在超时的风险,跟优先级有关,没有必要,更倾向于mid=i+(j-i)/2的形式,毕竟代码是给人看的,这种写法传达的信息就是为了保护不溢出。

复杂度

时间复杂度O(log2N):特殊情况下,会退化为线性的复杂度,跟给定的数组有关。

空间复杂度O(1):未使用额外的内存空间,为常数级别。

链接

旋转数组的最小数字