leetcode-33-搜索旋转排序数组

1,693 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天,点击查看活动详情

题目地址

整数数组 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,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] <= 104
  • nums 中的每个值都 独一无二
  • 题目数据保证 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-搜索旋转排序数组

如有任何问题或建议,欢迎留言讨论!