33. 搜索旋转排序数组

238 阅读2分钟

思路

  • 使用二分查找可以达到O(logN)的时间复杂度。
  • “旋转”的意思是按照顺序从nums的头部抽出几个元素然后按照顺序插到nums的尾部,因此旋转后的数组是两个升序子数组构成的。
  • 用二分法一定能保证[lo, mid] [mid + 1, hi]这两个区间肯定有一个是升序的,另一个是升序/乱序的。
  • 二分查找的本质:每次循环放弃一半的元素,即选择mid左右两侧中的一侧,前提是必须保证整个数组是有序的,否则无法判断targetmid的大小关系来选择哪一边。

所以,这个题的关键在于搞清楚放弃其中一半的条件。

方法一:直接二分查找

72645F6DA54AAB12427580B63F0B4565.png

class Solution {
    public int search(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {//【情况A1,A2】
                return mid;
            } else if (nums[mid] >= nums[left]) {
                if (target < nums[mid] && target >= nums[left]) {//满足这个条件,tar必在mid左侧的升序区间里【B1】
                    right= mid - 1;
                } else {
                    left = mid + 1;//不满足if条件,表明tar必不在mid左侧的升序区间里,可以放弃左侧【C1】
                }
            } else {//如果后半部分有序
                if (target > nums[mid] && target <= nums[right]) {//满足这个条件,tar必在mid右侧的升序区间里【B2】
                    left = mid + 1;
                } else {
                    right = mid - 1;//不满足if条件,表明tar必不在mid右侧升序区间里,可以放弃右侧【C2】
                }
            }
        }
        return -1;
    }
}

其中,

if (target < nums[mid] && target >= nums[lo])

要写target >= nums[lo],这样才可以把区间限制死,否则会出现这种情况:3456712中的12

方法二:先O(logN)找旋转点,再分别二分查找

最小点的前一个点一定是升序结束点

class Solution {
    public int search(int[] nums, int target) {
        int rp = findMin(nums) - 1;//rp为升序结束点的索引
        int left = binarySearch(nums, target, 0, rp);
        int right = binarySearch(nums, target, rp + 1, nums.length - 1);
        if (left == -1 && right == - 1) {
            return -1;
        } else {
            return Math.max(left, right);
        }
    }
    public int findMin(int[] nums) {
        int lo = 0;
        int hi = nums.length - 1;
        int min = Integer.MAX_VALUE;
        int minIdx = 0;
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;
            if (nums[mid] >= nums[lo]) {//左半部分升序
                if (nums[lo] < min) {
                    min = nums[lo];
                    minIdx = lo;
                }
                lo = mid + 1;//搜索右半部分
            } else {//右半部分升序
                if (nums[mid] < min) {
                    min = nums[mid];
                    minIdx = mid;
                }
                hi = mid - 1;//搜索左半部分
            }
        }
        return minIdx;
    }
    public int binarySearch(int[] nums, int target, int lo, int hi) {
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] > target) {
                hi = mid - 1;
            } else if (nums[mid] < target) {
                lo = mid + 1;
            }
        }
        return -1;
    }
}