「这是我参与2022首次更文挑战的第12天,活动详情查看:2022首次更文挑战」
题目
已知存在一个按非降序排列的整数数组 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
题解
枚举
数组中是否存在目标值target,一次循环查找数组中所有元素找到target返回true否则返回false即可
但是,这样时间复杂度为O(n);并且没有用到数组是有规律的这个特性。并且题目中明确要求必须尽可能减少整个操作步骤。
有规律的数组寻找目标值,常见的的方式是二分
二分法
因为数组在有序的基础上进行了旋转,所以原数组应该是1个或者2个有序数组组成的;
- 当旋转下标是0时,数组有序;
- 当旋转下标为k时,数组在区间有序
知道这个规律,首先通过枚举找到k,在通过二分法找到 target;
这里用到了二分法,但是并不是最优解,只是更容易理解的二分法
var search = function(nums, target) {
const len = nums.length;
let f = 0
for(let i = 1 ; i< len ; i++){
if(nums[i] >= nums[i-1]){
f = i
}else{
break
}
}
return helper(0,f) || helper(f+1,len)
function helper (left,right){
while(left <= right){
const m = left +( (right - left) >>1)
if(nums[m] === target) return true;
if(nums[m] > target){
right = m-1
}else{
left = m+1
}
}
return false
}
};
最优解二分法
数组虽然经过旋转,但是数组的规律还在,在二分的时候,一定最少有一个数组是有序的;
常规二分法在[0,n]区间;
- ;
- ,在区间查找
- ,在区间查找
- ,返回
旋转数组,二分过程中可能会产生一个无序的数组,所以要找到这个无序数组
- ;
从这里就开始变化了- ,如果中间值大于左侧值,数组在区间有序,
- , 一定在区间
- 否则在区间
- ,数组左侧大于右侧,这是个无序数组,无序数组的另一侧时有序数组
- 通过 添加成立判断 是否在[mid+1,right]
- 否则在[left,mid]
- ,特殊处理,因为数组中的值不必互不相同,遇到[1,0,1,1,1];
- 此时应该如果目标值 返回
- 否则尝试在 区间查找
根据上述思路编辑代码如下
var search = function (nums, target) {
let left = 0
let right = nums.length - 1
while (left < right) {
let mid = left + Math.floor(right - left) / 2
if (nums[left] < nums[mid]) {
if (nums[left] <= target && target <= nums[mid]) {
right = mid
} else {
left = mid + 1
}
} else if (nums[left] > nums[mid]) {
if (nums[mid] < target && target <= nums[right]) {
left = mid + 1
} else {
right = mid
}
} else {
if (nums[left] == target) return true
left = left + 1
}
}
return nums[left] == target
}