[前端]_一起刷leetcode 81. 搜索旋转排序数组 II

167 阅读5分钟

这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战

题目

81. 搜索旋转排序数组 II

已知存在一个按非降序排列的整数数组 nums ,数组中的值不必互不相同。

在传递给函数之前,nums 在预先未知的某个下标 k0 <= k < nums.length)上进行了 旋转 ,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,4,4,5,6,6,7] 在下标 5 处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4] 。

给你 旋转后 的数组 nums 和一个整数 target ,请你编写一个函数来判断给定的目标值是否存在于数组中。如果 nums 中存在这个目标值 target ,则返回 true ,否则返回 false 。

你必须尽可能减少整个操作步骤。

 

示例 1:

输入: nums = [2,5,6,0,0,1,2], target = 0
输出: true

示例 2:

输入: nums = [2,5,6,0,0,1,2], target = 3
输出: false

 

提示:

  • 1 <= nums.length <= 5000
  • -104 <= nums[i] <= 104
  • 题目数据保证 nums 在预先未知的某个下标上进行了旋转
  • -104 <= target <= 104

思路

  1. 这道题目是我们上一道题目[路飞]_一起刷leetcode 33. 搜索旋转排序数组的延伸,不过这道题目加了难度,考察的点多了一个可以重复,这个重复的概念一开始我们并不清楚有什么影响,我们先忽略它正常做题;
  2. 老规矩,在指定的区间中查找某个元素,用二分排序来查找,但是传统的二分算法是当前值如果小于目标值,就把左边界右移,否则把右边界往左移动,在这道题目中需要加个特殊判断,因为数组是经过旋转的,意味着最小的那一部分可能会出现在数组的最后面;
  3. 所以我们判断边界左移和右移之前,得先判断一下当前数组元素的大小关系,如果当前元素位于前面部分升序排序的,同时前面的最小值大于我们的目标值,说明我们要查找的目标值被旋转到最后面去了,这时候我们的边界要右移,其他情况下小于目标值的都按照正常的边界左移进行;
  4. 如果我们的当前值小于于目标值,常规来说目标值在当前值的右侧,但是这道题目我们需要做个判断,如果当前值处于旋转后的那一部分数组的话,我们需要判断一下最后一个元素是否大于目标值,如果小于,意味着当前处于比较小的被旋转的部分,那么我们的目标值会处于当前值的左侧,右边界左移即可;
  5. 搞明白了这两个大小关系后,我们按照正常的二分思路来实现即可。

举个例子:

[4,0,1,2] => 我们要找到 4 ,第一次进来我们会找到0, 正常来说我们会把左边界往右移,但是在这道题目中,我们需要判断最后一个元素2是否大于4, 如果小于4的话说明当前部分是被旋转的部分比较小的元素集合。

同理

[4,5,6,2] => 我们要找到 2 ,第一次进来我们会找到5, 正常来说我们会把右边界往左移,但是在这道题目中,我们需要判断第一个元素4是否小于2, 否则说明我们要找的部分被旋转到后面去了,我们需要把左边界右移。

实现

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {boolean}
 */
var search = function(nums, target) {
    let left = 0, right = nums.length - 1;

    while (left < right) {
        const mid = Math.floor((left + right) / 2);

        if (nums[mid] === target) {
            return true;
        } else if (nums[mid] > target) {
            // 大于的时候,我们判断一下在前半截还是后半截
            // 如果在前半截我们需要判断前面的最小值和当前值的关系
            if (nums[mid] >= nums[0] && nums[0] > target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        } else {
            // 小于的时候,我们判断一下后面的最大值和当前值的关系
            if (nums[mid] >= nums[0] && nums[nums.length - 1] < target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
    }

    return false;
};

翻车

image.png

看到这里我才意识到,重复元素就是个大坑,你全部重复的,就一个不同,不经过遍历很难找出到底哪个是分割点。难道这样子我们就放弃了嘛? 当然不是,我们直接给它进行去重,然后做同样的操作就可以了。不过进行去重的话就没必要这么麻烦了,还不如直接用findIndex一行代码梭哈。那么我们只能在原有基础上做优化。遇到这种特殊的,分不清哪边是升序哪边是降序的,我们就把两边边界都往里面移动一个格子,直到我们能分清楚或者剩下一个元素为止。

最终代码

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {boolean}
 */
var search = function(nums, target) {
    let left = 0, right = nums.length - 1;

    while (left <= right) {
        const mid = Math.floor((left + right) / 2);

        if (nums[mid] === target) {
            return true;
        } else if (nums[left] === nums[mid] && nums[right] === nums[mid]) {
            left++;
            right--;
        } else if (nums[mid] > target) {
            // 大于的时候,我们判断一下在前半截还是后半截
            // 如果在前半截我们需要判断前面的最小值和当前值的关系
            if (nums[mid] >= nums[left] && nums[left] > target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        } else {
            // 小于的时候,我们判断一下后面的最大值和当前值的关系
            if (nums[mid] < nums[left] && nums[right] < target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
    }

    return false;
};

最终结果

image.png

看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。