6-LeetCode剑指 Offer 11. 旋转数组的最小数字

289 阅读2分钟

一、题目

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

二、题解

这题两个方法,第一种方法是最容易想到的就是直接遍历数组找那个断点,第二种则是二分法,速度相比更快,但是逻辑更复杂,我们具体来解析一下:

1.常规遍历法:就是遍历数组,然后找到前一个值突然大于后面个值的状况,也就是numbers[i]>numbers[i+1]的情况,这时候那个最小值(断点)就是numbers[i+1]。具体来说样例1,就是3,4,5,1,2,遍历到5之前,前面都是递增的,然后下一个值突然出现了1,发现5>1,所以可得1就为断点,输出即可,然后还有种特殊情况,本来就是完全递增的数组,这个时候直接输出第一个值numbers[0]即可。

2.二分法:一般用于查找数,这里用于查找断点,其实本质差不多,但是有一些小区别,用left表示范围左边,right表示范围右边,mid=Math.floor((left+right)/2),注意!!!这里要用Math.floor向下取整!因为js是弱类型,如果不用floor函数的话,mid算出来是小数!所以这里floor向下取整是为了保证mid是整数!然后循环判断,当number[mid]>numsber[right],断点就在mid右边(不可以为mid),当number[mid]<numsber[right],断点就在mid左边(且可以为mid),如果number[mid]==number[right],无法判断右边还是左边,则缩小范围:right--。二分结束后直接return numbers[left]或者numbers[right]即可。

三、代码(js)

//1.常规逻辑方法 遍历数组找断点
// /**
//  * @param {number[]} numbers
//  * @return {number}
//  */

// let minArray = function(numbers) {
//     for(let i = 0 , len = numbers.length;  i < len ; i++)
//     {
//          if(numbers[i]>numbers[i+1])
//          {
//              return numbers[i+1];
//          } 
//     }
//     return numbers[0];
// };


// 2.二分法
/**
 * @param {number[]} numbers
 * @return {number}
 */
let minArray = function(numbers) {
    let left = 0;
    let right = numbers.length-1; 
    while(left<right)
    {
        //js是弱类型语言,这个mid一开始算出来是0.5!!!!需要用floor向下取整(向上取整为celi)
        let mid = Math.floor((left+right)/2); 
        //当中间的数大于右边的数时,说明断点在mid右边
        if(numbers[mid]>numbers[right])
        {
            left=mid+1;
        }     
        //当中间的数小于右边的数时,说明断点在左边,且可能就是mid
        else if(numbers[mid]<numbers[right])
        {
            right=mid;
        } 
        //相等则用right--来缩短范围
        else
        {
            right--
        }
    }
    //这里二分完毕之后,left和right其实是一样的
    return numbers[left];
};

四、拓展思考

提二分法中的几个点:

1.let mid = Math.floor((left+right)/2)这地方以后记得一定要加floor函数,js弱类型,mid会根据计算结果就变为小数了,所以必须要向下取整

2.为什么是用numbers[mid]和numbers[right]比较而不是用numbers[mid]和numbers[left]比较?举个例子,比如[1,2,3,4,5]和[5,6,7,1,2,3],显然numbers[mid]都>numbers[left],但是一个断点在左边,一个断点在右边,无法判断。

3.为什么一个numbers[mid]>numbers[right]判断之后是left=mid+1,而numbers[mid]<numbers[right]之后是right=mid?(也就是为啥left=mid还要加一?)因为当numbers[mid]>numbers[right]时,断点一定不是numbers[mid],因为断点肯定小于numbers[right],所以这时候left=mid+1。反之,当numbers[mid]<numbers[right]时,断点可能就是mid,所以right=mid。

4.为什么numbers[mid]==numbers[right]时要right--,这里原因比较复杂,想具体了解可以去leetcode的题解里面看看,链接就在下面。我的理解就是当numbers[mid]==numbers[right]时就无法判断断点是在左边还是在右边,那就无法将mid赋值left或者right,所以就只好缩小范围,所以就用right--。

题目来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/xu…