搜索旋转排序数组

题面

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

分析

  • 朴素的想法是,先使用二分找到进行左旋转的位置,此时二分的判断逻辑是什么?如果中间元素小于尾元素,说明最小值在左区间;否则在右区间;找到最小值的下标 i 后,如果 target 大于尾元素,说明 target 在 0-i 之间,否则说明在 i-nums.size()-1 区间内,实现代码如下

    class Solution {
        // 153. 寻找旋转排序数组中的最小值(返回的是下标)
        int findMin(vector<int>& nums) {
            int left = -1, right = nums.size() - 1; // 开区间 (-1, n-1)
            while (left + 1 < right) { // 开区间不为空
                int mid = left + (right - left) / 2;
                if (nums[mid] < nums.back()) {
                    right = mid;
                } else {
                    left = mid;
                }
            }
            return right;
        }
    
        // 有序数组中找 target 的下标
        int lower_bound(vector<int>& nums, int left, int right, int target) {
            while (left + 1 < right) { // 开区间不为空
                // 循环不变量:
                // nums[right] >= target
                // nums[left] < target
                int mid = left + (right - left) / 2;
                if (nums[mid] >= target) {
                    right = mid; // 范围缩小到 (left, mid)
                } else {
                    left = mid; // 范围缩小到 (mid, right)
                }
            }
            return nums[right] == target ? right : -1;
        }
    
    public:
        int search(vector<int>& nums, int target) {
            int i = findMin(nums);
            if (target > nums.back()) { // target 在第一段
                return lower_bound(nums, -1, i, target); // 开区间 (-1, i)
            }
            // target 在第二段
            return lower_bound(nums, i - 1, nums.size(), target); // 开区间 (i-1, n)
        }
    };
  • 实际上,可以将两个二分过程合并,设 x 是现在取到的数,那么其关系如下

    • 如果 x 和 target 在不同的递增段:
      • 如果 target 在第一段,x 在第二段,说明 target 在 x 的左边
      • 如果 x 在第一段,target 在第二段,说明 target 在 x 的右边
    • 如果 x 和 target 在相同的递增段:
      • 和 lowerBound 函数一样,比较 x 和 target 的大小即可
  • 代码实现

    class Solution {
    public:
        int search(vector<int>& nums, int target) {
            int last = nums.back();
            int left = -1, right = nums.size() - 1; // 开区间 (-1, n-1)
            while (left + 1 < right) { // 开区间不为空
                int mid = left + (right - left) / 2;
                int x = nums[mid];
                if (target > last && x <= last) { // target 在第一段,x 在第二段
                    right = mid; // 下轮循环去左边找
                } else if (x > last && target <= last) { // x 在第一段,target 在第二段
                    left = mid; // 下轮循环去右边找
                } else if (x >= target) { // 否则,x 和 target 在同一段,这就和方法一的 lower_bound 一样了
                    right = mid;
                } else {
                    left = mid;
                }
            }
            return nums[right] == target ? right : -1;
        }
    };
  • 一种分治思想是,将旋转后的数组分为左右两个数组,两个数组中必定有一个是有序的,而根据子数组的首尾元素比较就可以确定是否有序;这样一直划分,最后一定能划分到完全有序的子数组

  • 实现代码

    class Solution {
    public:
        int binarySearch(vector<int>& nums, int target, int left, int right) {
            int mid = 0;
            while (left <= right) {
                mid = left + (right - left) / 2;
                if (nums[mid] < target) {
                    left = mid + 1;
                } else if (nums[mid] > target) {
                    right = mid - 1;
                } else {
                    return mid;
                }
            }
            return -1;
        }
        int searchChildArray(vector<int>& nums, int target, int left, int right) {
            // 递归终止条件:区间无效/只有一个元素
            if (left > right)
                return -1;
            if (left == right)
                return nums[left] == target ? left : -1;
    
            int mid = left + (right - left) / 2;
            if (nums[left] <= nums[mid]) {
                int res = binarySearch(nums, target, left, mid);
                return res != -1 ? res
                                 : searchChildArray(nums, target, mid + 1, right);
            }
            else {
                int res = binarySearch(nums, target, mid + 1, right);
                return res != -1 ? res : searchChildArray(nums, target, left, mid);
            }
        }
    
        int search(vector<int>& nums, int target) {
            if (nums.empty())
                return -1;
            return searchChildArray(nums, target, 0, nums.size() - 1);
        }
    };
这篇文档有帮助吗?

On this page

页面信息