33. 搜索旋转排序数组

189 阅读5分钟

【题目】

整数数组 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
  • -10^4 <= nums[i] <= 10^4
  • nums 中的每个值都 独一无二
  • 题目数据保证 nums 在预先未知的某个下标上进行了旋转
  • -10^4 <= target <= 10^4

【题目解析】

思路

二分查找法

二分查找是解决这类问题的理想选择,但由于数组被旋转,我们不能直接应用传统的二分查找。解决方案在于,即使数组被旋转,我们仍然可以通过比较中间元素与端点元素的值,确定哪一半是有序的,从而决定在哪一半中继续搜索。

  1. 初始设置:设置两个指针,left指向数组的起始位置,right指向数组的结束位置。

  2. 二分查找

    • 计算中间位置mid

    • 如果nums[mid]正好等于target,则直接返回mid

    • 判断左半部分是否有序:如果nums[left] <= nums[mid],则左半部分有序。

      • 在左半部分有序的情况下,如果target位于nums[left]nums[mid]之间,更新right指针;否则,更新left指针。
    • 否则,右半部分有序。

      • 在右半部分有序的情况下,如果target位于nums[mid]nums[right]之间,更新left指针;否则,更新right指针。
  3. 重复上述过程,直到找到目标值或left超过right

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums) - 1
        
        while left <= right:
            mid = (left + right) // 2
            if nums[mid] == target:
                return mid
                
            # 如果左侧是有序的
            if nums[mid] >= nums[left]:
                # 如果目标值在左侧有序部分
                if nums[left] <= target < nums[mid]:
                    right = mid - 1
                else:
                    left = mid + 1
            # 如果右侧是有序的
            else:
                # 如果目标值在右侧有序部分
                if nums[mid] < target <= nums[right]:
                    left = mid + 1
                else:
                    right = mid - 1
        
        # 如果没有找到
        return -1

执行

image.png

【总结】

“搜索旋转排序数组”问题是算法设计和数据结构领域中的一个经典问题,它要求在一个预先按升序排列但后来在未知下标上进行旋转的整数数组中高效地搜索一个给定的目标值。这个问题考验了算法设计者对二分查找法的深入理解及其在特定场景下的应用能力。

适用问题类型

这种使用二分查找法的变体适用于多种场景,尤其是那些涉及到部分有序数据集的问题,例如:

  • 在部分排序或完全排序的数组中查找元素。
  • 需要在最优时间复杂度内解决搜索问题,特别是数据量庞大时。
  • 数组经过某种已知或未知的变换,但仍保留了一定的有序性质。

使用的算法:二分查找法的变体

针对“搜索旋转排序数组”问题,使用的是二分查找法的一个变体。传统的二分查找依赖于数组的完全有序性,而本问题中的数组虽然整体被旋转,但在任意时刻仍然可以分为两部分,其中至少一部分保持有序。算法的核心在于如何利用这一性质来判断目标值可能存在的区域,并相应地调整搜索范围。

算法解析

  • 时间复杂度:O(log n),其中n是数组的长度。尽管数组被旋转,算法通过每次减半搜索范围仍保持了对数级的时间复杂度。
  • 空间复杂度:O(1),算法在原地执行,不需要额外的存储空间。

算法优化和实践意义

该算法的优化主要集中在减少比较操作和更快地确定有序区间上。在实践中,理解和掌握这种算法可以帮助解决一系列复杂的搜索问题,尤其是在数据预处理或排序不完全可控的实际应用场景中。例如,它可以应用于系统日志分析、时间序列数据处理、甚至是在一些数据库查询优化中。

总结

掌握“搜索旋转排序数组”问题的解决方法,不仅可以提高个人的算法设计和问题解决能力,还能深化对二分查找等经典算法在复杂场景应用的理解。这种算法思维的训练和提升对于软件开发人员、数据科学家等技术专业人员来说具有重要的实践价值和长远意义。

题目链接

搜索旋转排序数组