这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战
题目
81. 搜索旋转排序数组 II
已知存在一个按非降序排列的整数数组 nums
,数组中的值不必互不相同。
在传递给函数之前,nums
在预先未知的某个下标 k
(0 <= 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
思路
- 这道题目是我们上一道题目[路飞]_一起刷leetcode 33. 搜索旋转排序数组的延伸,不过这道题目加了难度,考察的点多了一个可以重复,这个重复的概念一开始我们并不清楚有什么影响,我们先忽略它正常做题;
- 老规矩,在指定的区间中查找某个元素,用二分排序来查找,但是传统的二分算法是当前值如果小于目标值,就把左边界右移,否则把右边界往左移动,在这道题目中需要加个特殊判断,因为数组是经过旋转的,意味着最小的那一部分可能会出现在数组的最后面;
- 所以我们判断边界左移和右移之前,得先判断一下当前数组元素的大小关系,如果当前元素位于前面部分升序排序的,同时前面的最小值大于我们的目标值,说明我们要查找的目标值被旋转到最后面去了,这时候我们的边界要右移,其他情况下小于目标值的都按照正常的边界左移进行;
- 如果我们的当前值小于于目标值,常规来说目标值在当前值的右侧,但是这道题目我们需要做个判断,如果当前值处于旋转后的那一部分数组的话,我们需要判断一下最后一个元素是否大于目标值,如果小于,意味着当前处于比较小的被旋转的部分,那么我们的目标值会处于当前值的左侧,右边界左移即可;
- 搞明白了这两个大小关系后,我们按照正常的二分思路来实现即可。
举个例子:
[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;
};
翻车
看到这里我才意识到,重复元素就是个大坑,你全部重复的,就一个不同,不经过遍历很难找出到底哪个是分割点。难道这样子我们就放弃了嘛? 当然不是,我们直接给它进行去重,然后做同样的操作就可以了。不过进行去重的话就没必要这么麻烦了,还不如直接用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;
};
最终结果
看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。