33. 搜索螺旋数组

题目描述:

整数数组 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. 数组的某一部分是升序排列的,但另一部分因为旋转的原因可能并不是升序排列。
  2. 通过找到旋转点,我们能够把数组划分成两个部分,一部分是递增的,另一部分在旋转后也保持递增但次序不同。

在这种情况下,传统的二分查找算法无法直接使用,因为整个数组并不是完全有序的。不过,可以通过观察发现,每次将数组一分为二时,总有一部分是有序的,这是我们可以利用的特性。

思路分析

  1. 确定二分查找的基础:二分查找的核心是在每一步将搜索空间减半。我们每次通过将数组分成两部分,分别检查目标值是否在其中一部分的有序区间内,从而决定搜索的方向。

  2. 如何判断哪部分是有序的:

    • 每次通过比较 nums[left] 和 nums[mid] 来判断左半部分是否有序。
    • 如果 nums[left] <= nums[mid],则说明左半部分是有序的。因为旋转不会影响这一部分,nums[left] 到 nums[mid] 保持递增。
    • 否则,右半部分一定是有序的,即 nums[mid] <= nums[right]。
  3. 在有序区间内查找目标值:

    • 如果左半部分是有序的,并且目标值 target 落在这个有序区间中,那么我们缩小搜索范围到左半部分,即更新 right = mid - 1。
    • 如果目标值不在左半部分有序区间内,则我们将搜索范围缩小到右半部分,即更新 left = mid + 1。
  4. 反之亦然:如果发现右半部分是有序的,执行同样的判断。如果目标值在右半部分有序区间内,就在右侧继续查找;否则,继续在左侧查找。

  5. 终止条件:每次通过调整 left 和 right 指针,最终要么找到目标值,要么当 left > right 时,确定目标值不存在。

思路示例

以数组 [4, 5, 6, 7, 0, 1, 2] 和 target = 1 为例,具体操作如下:

  • 初始状态:

    • left = 0,right = 6,mid = 3,nums[mid] = 7。
    • 比较 nums[left] = 4 和 nums[mid] = 7,确定左半部分 [4, 5, 6, 7] 是有序的。
    • 目标 1 不在这个有序区间内,搜索右半部分 [0, 1, 2]。
  • 第二次查找:

    • 更新 left = 4,right = 6,mid = 5,nums[mid] = 1。
    • 找到目标值 1,返回 mid = 5。

这个过程利用了旋转数组的有序性特征,确保了每次都能在正确的区间内查找目标值。
点击查看代码

func search(nums []int, target int) int {
    left, right := 0, len(nums) - 1

    while left <= right {
        mid := left + (right - left) / 2
        
        // 找到目标值
        if nums[mid] == target {
            return mid
        }

        // 判断左半部分是否有序
        if nums[left] <= nums[mid] {
            // 如果目标值在左半部分的有序区间内
            if target >= nums[left] && target < nums[mid] {
                right = mid - 1
            } else {
                left = mid + 1
            }
        } else {
            // 右半部分有序
            if target > nums[mid] && target <= nums[right] {
                left = mid + 1
            } else {
                right = mid - 1
            }
        }
    }

    // 目标值不存在
    return -1
}