持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天,点击查看活动详情
整数数组 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,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1
示例 3:
输入: nums = [1], target = 0
输出: -1
提示:
1 <= nums.length <= 5000-104 <= nums[i] <= 104nums中的每个值都 独一无二- 题目数据保证
nums在预先未知的某个下标上进行了旋转 -104 <= target <= 104
解题思路-范围二分
在有序数组中,想要加速查找目标值,可以使用二分算法。但是本题中输入数组在某个位置进行了旋转,所以就需要先找到翻转位置,然后根据该位置值和 target 的大小关系,判断 target 处于那一部分,然后对该部分进行二分即可。
代码实现
function find(nums,l,r,target){
while(l<r){
const mid = (l+r)>>1
if(nums[mid]<target){
l = mid+1
}else{
r = mid
}
}
return nums[l]===target?l:-1
}
var search = function(nums, target) {
const len = nums.length
if(nums[0]<=nums[len-1]){
return find(nums,0,len-1,target)
}
for(let i = 1;i<len;i++){
if(nums[i]<nums[i-1]){
if(nums[0]<=target){
return find(nums,0,i-1,target)
}
return find(nums,i,len-1,target)
}
}
}
解题思路-整体二分
上面的算法的时间复杂度是 O(m+log(n-m),与本题要求的 O(log n) 还是有一点差距的。
想要实现 O(log n) 的时间复杂度,就需要对整个数组采用二分。但是二分只能对有序数组使用,而本题中输入数组是在有序数组的基础上进行了旋转,如何使用二分呢?
其实我们可以在每次更新 mid 后通过判断 nums[mid] 落在哪个区间,然后根据 nums[mid],target 以及收尾元素的大小关系更新 l 或者 r。这样就可以不需要先找到旋转位置再进行二分了。
代码实现
var search = function(nums, target) {
const len = nums.length
if(len === 1){
return nums[0]===target?0:-1
}
let l = 0
let r = len-1
while(l<=r){
const mid = (l+r)>>1
if(nums[mid] === target){
return mid
}
// 落在前半区间
if(nums[0]<=nums[mid]){
if(nums[0]<=target && target<nums[mid]){
r = mid-1
}else{
l = mid+1
}
// 落在后半区间
}else{
if(nums[mid]<target && target <= nums[len-1]){
l = mid+1
}else{
r = mid-1
}
}
}
return -1
}
至此我们就完成了 leetcode-33-搜索旋转排序数组
如有任何问题或建议,欢迎留言讨论!