听风是风

学或不学,知识都在那里,只增不减。

导航

JS Leetcode 154. 寻找旋转排序数组中的最小值 II 题解分析

壹 ❀ 引

早在10个月前,也就是去年,我记录了JS leetcode 寻找旋转排序数组中的最小值 题解分析,你不得不了解的二分法一题,那么这篇文章记录它的升级版,来自LeetCode154. 寻找旋转排序数组中的最小值 II,但是我现在回头看之前这篇文章,解题思路更像是找规律,有点难以记忆。我在前几天记录的另外两篇旋转数组中使用了相同的解题思路,所以为了思路上的统一,我想用相同的思路先把153. 寻找旋转排序数组中的最小值先重新梳理一遍,再来看它的升级版。回归正题,154. 寻找旋转排序数组中的最小值 II题目描述如下:

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,4,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,4]
若旋转 7 次,则可以得到 [0,1,4,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

给你一个可能存在 重复 元素值的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

示例 1:

输入:nums = [1,3,5]
输出:1

示例 2:

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

提示:

n == nums.length
1 <= n <= 5000
-5000 <= nums[i] <= 5000
nums 原来是一个升序排序的数组,并进行了 1 至 n 次旋转

进阶:

这道题是 寻找旋转排序数组中的最小值 的延伸题目。
允许重复会影响算法的时间复杂度吗?会如何影响,为什么?

贰 ❀ 思路分析与二分法

OK,我们还是先重新复习下153. 寻找旋转排序数组中的最小值这题,此题与升级版的题目唯一区别是数组中不存在重复项,即是一个数字不重复的被旋转的升序数组,让你找到数组中的最小值。

因为前面我们已经做了两题关于旋转数组的题目,已经总结出了一种二分法的思路,这里直接再次套用这种解题思路,比如要找到[4,5,0,1,2,3]中的最小值,我们依然可以用mid与nums[0]进行比较,如果mid>=nums[0]说明升序部分在左侧,如果mid<nums[0]说明升序部分在右侧,我们用图画出来,感受下这个规律:

图1

如上图,通过中间数mid与nums[0]的大小比较就能知道升序在哪一边。如果升序在这一边,那么最小值一定在不是升序的这一边,看图自己感受下。当然有个特例,数组没旋转的情况下,也满足mid>=nums[0],但最小值其实在左侧,没关系,因为只有未旋转的数组nums[0]<=nums[length-1],比如:

let arr = [1];
arr[0]<=arr[arr.length-1]; //true
let arr = [1,2]
arr[0]<=arr[arr.length-1]; //true

让我们来实现这段代码:

var findMin = function (nums) {
    let n = nums.length;
    let l = 0;
    let r = n - 1;
    // 数组未旋转或者数组只有一位的情况
    if (nums[l] <= nums[r]) {
        return [nums[l]];
    };
    // 能走到这,说明数组一定旋转了
    while (l < r) {
        let mid = Math.floor((l + r) / 2);
        // 如果mid>=nums[0],升序在左侧,最小在右侧,修改l的指针
        if (nums[mid] >= nums[0]) {
            // 为什么要加1,请看图感受
            l = mid+1;
        } else {
            //反之升序在右侧,最小值在左侧,修改r指针
            r = mid;
        }
    }
    return nums[l] <= nums[r] ? nums[l] : nums[r];
};

我们来解释下这段代码中的几个注意点,第一个问题,为什么是nums[mid] >= nums[0],因为我们取mid是向下取整,比如数组[2,1],mid取的其实是nums[0]也就是2,因为nums[mid]其实等于nums[0],其实就是为了兼容这种情况。

第二个问题,为什么l=mid+1,而r不是mid-1呢?其实看我们上面列举的旋转的数组图示,你会发现当mid>=nums[0]时,由于升序在左侧,最小值在右侧,而且最小一定不可能是mid,所以直接mid+1,不然假设数组是[2,1]的情况,你会发现由于l=mid会一直等于0,然后陷入死循环。

l不是mid-1看图其实也很清楚,因为有可能mid就是最小值,所以我们不能舍弃掉,这也算是画图找规律的一种,但整体思路其实还是与之前两道题相同。

OK,那么到这里我们用之前mid与nums[0]比较的思路,重新把这道题解了下,算是把思路给统一了,不然一道题一个思路也确实难以记忆。

那么回到它的升级版,也就是存在重复数的情况,我们甚至可以暴力点,直接数组去重后套用上面的代码,比如:

/**
 * @param {number[]} nums
 * @return {number}
 */
var findMin = function (nums) {
    // 去重
    nums = [...new Set(nums)]
    // 下面不变
    let n = nums.length;
    let l = 0;
    let r = n - 1;
    // 数组未旋转或者数组只有一位的情况
    if (nums[l] <= nums[r]) {
        return [nums[l]];
    };
    // 能走到这,说明数组一定旋转了
    while (l < r) {
        let mid = Math.floor((l + r) / 2);
        // 如果mid>=nums[0],升序在左侧,最小在右侧,修改l的指针
        if (nums[mid] >= nums[0]) {
            // 为什么要加1,请看图感受
            l = mid+1;
        } else {
            //反之升序在右侧,最小值在左侧,修改r指针
            r = mid;
        }
    }
    return nums[l] <= nums[r] ? nums[l] : nums[r];
};

当然既然题目都给了重复数字,咱们直接去重多少差点意思,如果不去重我们如何解决这道题呢?

举个例子,假设数组为[3,1,3],上面的代码会直接挂掉,因为一开始就满足了nums[l] <= nums[r],其实在JS Leetcode 81. 搜索旋转排序数组 II 题解,补救二分法的可行性中,我们就解释了重复元素导致二分法特性丢失的问题,而挽救这种特性的办法就是,当数组的nums[l]===nums[r]时,我让任意一方的指针进行缩进,比如r++即可,因为给无数个3和给一个3,并不会影响到最终的结果判断。

所以我们修改下上方的代码,不用去重就是这样:

var findMin = function (nums) {
    let n = nums.length;
    let l = 0;
    let r = n - 1;
    while (l < r) {
				// 只要左右指针相同,就让r--,比如[3,1,3]就会变成[3,1]
        if (nums[l] <= nums[r]) {
            return [nums[l]];
        };
        // 这个移进来了,如果放外面可能有[3,1,3]这种,同时也用于解决1个元素的情况以及未旋转的情况
        if (nums[l] === nums[r]) {
            r--;
            break;
        };
        let mid = Math.floor((l + r) / 2);
        // 如果mid>=nums[0],升序在左侧,最小在右侧,修改l的指针
        if (nums[mid] >= nums[0]) {
            // 为什么要加1,请看图感受
            l = mid + 1;
        } else {
            //反之升序在右侧,最小值在左侧,修改r指针
            r = mid;
        }
    }
    return nums[l] <= nums[r] ? nums[l] : nums[r];
};

那么本文就到这里了,相关系列题型可见下方。

JS leetcode 153. 寻找旋转排序数组中的最小值 题解分析,你不得不了解的二分法

JS Leetcode 33. 搜索旋转排序数组题解,图解旋转数组中的二分法

JS Leetcode 81. 搜索旋转排序数组 II 题解,补救二分法的可行性

posted on 2021-04-11 20:04  听风是风  阅读(110)  评论(2编辑  收藏  举报