33. 搜索旋转排序数组 + 二分法 + 旋转数组

题目来源

LeetCode_33

相似题目

33. 搜索旋转排序数组
153. 寻找旋转排序数组中的最小值
154. 寻找旋转排序数组中的最小值 II

题目描述

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

示例 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

进阶: 你可以设计一个时间复杂度为 O(log n) 的解决方案吗?

题解分析

解法一

  1. 题目实际考察的是二分法。
  2. 虽然旋转后的数组是部分有序的,但是由于每次迭代都必然有一部分(左部分或者右部分)是有序的,此时可以判断当前数是否在有序的那部分,进而控制上下界。
package com.walegarrett.interview;

/**
 * @Author WaleGarrett
 * @Date 2021/2/28 19:21
 */

/**
 * 题目描述:整数数组 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 。
 */
public class LeetCode_33 {
    public int search(int[] nums, int target) {
        int len = nums.length;
        int low=0, high = len-1;
        while(low <= high){
            int mid = (low+high)>>1;
            if(nums[mid] == target)
                return mid;
            if(nums[0] <= nums[mid]){//前半部分有序
                if(nums[mid] > target && nums[0] <= target)
                    high = mid-1;
                else low = mid + 1;
            }else{//后半部分有序
                if(nums[mid] < target && nums[len-1] >= target)
                    low = mid + 1;
                else high = mid - 1;
            }
        }
        return -1;
    }
}

解法二

  1. 本题与传统二分的最大区别就是这里的数组是分段有序的,总共就有两种分段,而mid落在哪个分段是不确定的。
  2. 我们可以分情况考虑,因为每次求解出的mid和区间的分布总共就三种情况,如下:
    左、中、右三个位置的值相比较,有以下几种情况:
    • 左值 < 中值, 中值 < 右值 :没有旋转,最小值在最左边,可以收缩右边界

      		右
      	 中
       左
      
      
    • 左值 > 中值, 中值 < 右值 :有旋转,最小值在左半边,可以收缩右边界

       左       
      		 右
      	 中
      
      
    • 左值 < 中值, 中值 > 右值 :有旋转,最小值在右半边,可以收缩左边界

      	 中  
       左 
      		 右
      
      
    • 左值 > 中值, 中值 > 右值 :单调递减,不可能出现

       左
      	中
      		右
      
      
  3. 我们可以首先根据以上的情况,分成两种情况来考虑,接着判断mid是在哪个区间,即左区间或者右区间,然后进行区间的缩小。
class Solution {
    public int search(int[] nums, int target) {
        int n = nums.length;
        int left = 0, right = n-1;
        while(left <= right){
            // 最多存在三种情况:
            //          右
            //      中
            //  左    

            //  左       
            //          右
            //      中        

            //       中  
            //  左 
            //          右
            int mid = left + (right - left) / 2;
            if(nums[mid] == target){
                return mid;
            }else if(nums[mid] >= nums[left]){// mid在左半部分
                if(target >= nums[left] && target <= nums[mid]){
                    right = mid - 1;
                }else{
                    left = mid + 1;
                }
            }else if(nums[mid] < nums[left]){// mid在右半部分
                if(target >= nums[mid] && target <= nums[right]){
                    left = mid + 1;
                }else{
                    right = mid - 1;
                }
            }
        }
        return -1;
    }
}
posted @ 2021-02-28 20:01  Garrett_Wale  阅读(75)  评论(0编辑  收藏  举报