热题100 - 33. 搜索旋转排序数组

61 阅读4分钟

题目描述:

整数数组 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

思路:

首先应该以logn的时间复杂度找到“反转”的位置。二分查找来实现。

找到之后,设计二分查找变种来适应这个旋转的数组。我有印象有个轮转数组的一个技巧,好像是分别反转。但是这里先不用了。

使用三次二分查找:

  • 第一次,找到“反转”的点。这里只能用“非标准”二分查找了,因为如果left=mid+1很可能会越过最大值。
  • 第二次,对“前半部分”二分查找,使用标准二分查找即可
  • 第三次,对“后半部分”二分查找,同上。

实现:

class Solution {
    public int search(int[] nums, int target) {
        // find position k
        int le = 0, ri = nums.length; // left close, right open
        int k;
        while (le < ri) {
            int mid = (le + ri) >> 1;
            if (nums[le] < nums[mid]) {
                le = mid;  // have to be a variant - otherwise may miss
            } else {
                ri = mid;
            }
        }
        k = le+1;  // exact largest value is le-1, but we want open.

        int lpl = 0, lpr = k;
        while (lpl < lpr) {
            int mid = (lpl + lpr) >> 1;
            if (target == nums[mid]) {
                return mid;
            }
            if (target < nums[mid]) {
                lpr = mid;
            } else {
                lpl = mid + 1;
            }
        }
        int rpl = k, rpr = nums.length;
        while (rpl < rpr) {
            int mid = (rpl + rpr) >> 1;
            if (nums[mid] == target) {
                return mid;
            }
            if (target < nums[mid]) {
                rpr = mid;
            } else {
                rpl = mid + 1;
            }
        }
        return -1;

    }
}

DS简评:

这段代码的目标是在旋转后的有序数组中查找目标值。其思路分为两步:首先找到旋转点将数组分为两个有序子数组,然后分别在两个子数组中进行二分查找。以下是详细分析:

思路分析

  1. 寻找旋转点(k)

    • 使用二分查找确定旋转点。旋转点将数组分为两个有序部分。例如,数组 [4,5,6,7,0,1,2] 的旋转点为索引3(值为7),分割后的子数组为 [4,5,6,7][0,1,2]
    • 循环逻辑:若中间元素大于左端元素,说明左半部分有序,将左边界右移;否则,旋转点在左半部分,右边界左移。循环结束时,k 设为 le + 1,即右半部分的起始位置。
  2. 二分查找目标值

    • 分别在左右两个有序子数组中执行标准二分查找。若在任一子数组中找到目标值,返回其索引;否则返回 -1

时间复杂度

  • 寻找旋转点:使用二分查找,时间复杂度为 O(log n)
  • 两次二分查找:每次查找的时间复杂度均为 O(log n)
  • 总体时间复杂度:三次独立的二分查找,总时间复杂度为 O(log n)

空间复杂度

  • 仅使用固定数量的变量,空间复杂度为 O(1)

代码亮点

  • 高效性:三次二分查找确保对数级别的时间复杂度。
  • 鲁棒性:正确处理无旋转、完全旋转等边界情况。

潜在问题

  • 重复元素:代码假设数组元素唯一。若存在重复元素,可能导致旋转点判断错误。
  • 极端情况:如单元素数组或未旋转数组,均能正确处理。

总结:该算法高效且简洁,适用于无重复元素的旋转有序数组查找问题。