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 。

输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4

输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1

输入:nums = [1], target = 0
输出:-1

思路

力扣:33. 搜索旋转排序数组_二分查找
将数组从中间分开成左右两部分的时候,一定有一部分的数组是有序的。

  • nums[0] <= nums[mid],0 ~ mid 不包含旋转(有序)
  • nums[mid] < nums[0],0 ~ mid 包含旋转(无序)

根据有序的那部分判断 target 的位置:

  • 如果 [l, mid - 1] 是有序数组,且 target 的大小满足 [nums[l],nums[mid]),则应该将搜索范围缩小至 [l, mid - 1],否则在 [mid + 1, r] 中寻找
  • 如果 [mid, r] 是有序数组,且 target 的大小满足 (nums[mid+1],nums[r]],则应该将搜索范围缩小至 [mid + 1, r],否则在 [l, mid - 1] 中寻找

代码

class Solution {
    public int search(int[] nums, int target) {
        int n = nums.length;
        if (n == 0) {
            return -1;
        }
        if (n == 1) {
            return nums[0] == target ? 0 : -1;
        }
        int l = 0, r = n - 1;
        while (l <= r) {
            int mid = (l + r) / 2;
            if (nums[mid] == target) {
                return mid;
            }
            if (nums[0] <= nums[mid]) {
                if (nums[0] <= target && target < nums[mid]) {
                    r = mid - 1;
                } else {
                    l = mid + 1;
                }
            } else {
                if (nums[mid] < target && target <= nums[n - 1]) {
                    l = mid + 1;
                } else {
                    r = mid - 1;
                }
            }
        }
        return -1;
    }
}

题目:81. 搜索旋转排序数组 II

已知存在一个按非降序排列的整数数组 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,4,4,5,6,6,7] 在下标 5 处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4] 。

给你旋转后的数组 nums 和一个整数 target ,请你编写一个函数来判断给定的目标值是否存在于数组中。如果 nums 中存在这个目标值 target ,则返回 true ,否则返回 false 。

输入:nums = [2,5,6,0,0,1,2], target = 0
输出:true

输入:nums = [2,5,6,0,0,1,2], target = 3
输出:false

思路

二分查找
和 33. 搜索旋转排序数组不同的是,本题元素并不唯一,无法直接根据与 nums[0] 的大小关系,将数组划分为两段。

二分查找时可能会有 a[l]=a[mid]=a[r],此时无法判断区间 [l,mid] 和区间 [mid+1,r] 哪个是有序的。

  • 第一类10 1 11 和 11 1 01
    此种情况下 nums[start] == nums[mid],分不清到底是前面有序还是后面有序,此时 start++ ,相当于去掉一个重复的干扰项。

  • 第二类234 5 671
    这种情况下 nums[start] < nums[mid],前半部分有序。若 nums[start] <=target<nums[mid],则在前半部分找,否则去后半部分找。

  • 第三类671 2 345
    这种情况下nums[start] > nums[mid],后半部分有序。若 nums[mid] <target<=nums[end],则在后半部分找,否则去前半部分找。

代码

public boolean search(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return false;
        }
        int start = 0;
        int end = nums.length - 1;
        int mid;
        while (start <= end) {
            mid = start + (end - start) / 2;
            if (nums[mid] == target) {
                return true;
            }
            if (nums[start] == nums[mid]) {
                start++;
                continue;
            }
            //前半部分有序
            if (nums[start] < nums[mid]) {
                //target在前半部分
                if (nums[mid] > target && nums[start] <= target) {
                    end = mid - 1;
                } else {  //否则,去后半部分找
                    start = mid + 1;
                }
            } else {
                //后半部分有序
                //target在后半部分
                if (nums[mid] < target && nums[end] >= target) {
                    start = mid + 1;
                } else {  //否则,去后半部分找
                    end = mid - 1;

                }
            }
        }
        //一直没找到,返回false
        return false;

    }

题目:153. 寻找旋转排序数组中的最小值

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次旋转后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

给你一个元素值互不相同的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的最小元素。

输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组

输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组

输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组

思路

一个不包含重复元素的升序数组在经过旋转之后,可以得到下面可视化的折线图:
力扣:33. 搜索旋转排序数组_二分查找_02
第一种情况是 nums[pivot]<nums[high]。说明 nums[pivot] 是最小值右侧的元素,忽略二分查找区间的右半部分。
力扣:33. 搜索旋转排序数组_最小值_03
第二种情况是 nums[pivot]>nums[high]。说明 nums[pivot] 是最小值左侧的元素,忽略二分查找区间的左半部分。
力扣:33. 搜索旋转排序数组_数组_04

代码

class Solution {
    public int findMin(int[] nums) {
        int low = 0;
        int high = nums.length - 1;
        while (low < high) {
            int pivot = low + (high - low) / 2;
            if (nums[pivot] < nums[high]) {
                high = pivot;
            } else {
                low = pivot + 1;
            }
        }
        return nums[low];
    }
}

题目:154. 寻找旋转排序数组中的最小值 II

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次旋转后,得到输入数组。例如,原数组 nums = [0,1,4,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,4]
若旋转 7 次,则可以得到 [0,1,4,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

给你一个可能存在重复元素值的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的最小元素。

输入:nums = [1,3,5]
输出:1

输入:nums = [2,2,2,0,1]
输出:0

思路

一个包含重复元素的升序数组在经过旋转之后,可以得到下面可视化的折线图:
力扣:33. 搜索旋转排序数组_二分查找_05
第一种情况是 nums[pivot]<nums[high]。说明 nums[pivot] 是最小值右侧的元素,忽略二分查找区间的右半部分。
力扣:33. 搜索旋转排序数组_数组_06
第二种情况是 nums[pivot]>nums[high]。说明 nums[pivot] 是最小值左侧的元素,忽略二分查找区间的左半部分。
力扣:33. 搜索旋转排序数组_每日一题_07
第三种情况是 nums[pivot]==nums[high]。由于重复元素的存在,并不能确定 nums[pivot] 是在最小值的左侧还是右侧,因此不能直接忽略某一部分的元素。
但由于它们的值相同,所以无论 nums[high] 是不是最小值,都有一个它的「替代品」nums[pivot],因此可以忽略二分查找区间的右端点。
力扣:33. 搜索旋转排序数组_数组_08

代码

class Solution {
    public int findMin(int[] nums) {
        int low = 0;
        int high = nums.length - 1;
        while (low < high) {
            int pivot = low + (high - low) / 2;
            if (nums[pivot] < nums[high]) {
                high = pivot;
            } else if (nums[pivot] > nums[high]) {
                low = pivot + 1;
            } else {
                high -= 1;
            }
        }
        return nums[low];
    }
}