LeetCode100之搜索旋转排序数组(33)--Java

1.问题描述

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

        难度等级

        中等

        题目链接

        搜索旋转排序数组

2.解题思路

        这道题我们仔细观察的话,其实本质还是一道二分查找的题目,只不过这个数组里面有两个二分查找的范围,我们需要确定应该在哪一个范围里去二分查找target。

        我们不难发现,数组的第一个元素,是第一个二分查找范围的最小值,数组的最后一个元素是第二个二分查找范围的最大值,我们可以根据target和这两个元素的大小关系来确定在哪一个二分范围查找target。

        如果target大于第二个二分查找的最大值又小于第一个二分查找的最小值,那个target一定不在这个数组里面,直接返回-1就可以了。

        //目标值大于最右边的数,小于最左边的数,找不到
        if(target > nums[nums.length - 1] && target < nums[0]){
            return -1;
        }

        接着,我们可以用一个变量dir来标识我们应该查找哪一个范围,如果dir为0,就查找左边的范围,dir为1就查找右边的范围。通过判断target和第二个范围的最大值的关系来确定dir的值,如果target小于等于第二个范围的最大值,那么target就有可能在第二个范围中,dir设置为1;反之,我们就在第一个范围中查找,dir设置为0。

        //0代表在最左边进行二分查找,1代表在最右边进行二分查找
        //目标值小于等于最右边的数,在最右边进行二分查找
        //目标值大于最左边的数,在最左边进行二分查找
        int dir = target <= nums[nums.length - 1] ? 1 : 0;

        然后,就是定义左右指针和二分指针进行二分查找了。二分查找的第一步是更新二分索引,接着判断二分索引指向的值是否等于target,是的话,直接返回。

        int left = 0;
        int right = nums.length - 1;
        int mid = 0;
        while(left <= right){
            //更新中间值
            mid = (right - left) / 2 + left;
            //如果中间值target
            if(nums[mid] == target){
                return mid;
            }
        .......
        }

        如果不是的话,在正常的二分查找步骤之前,我们还要先判断我们是否在我们要二分查找的范围之中。判断方法很简单,我们根据前面的dir的值,如果dir等于0,说明我们要在左边的二分范围里面查找,这个时候如果我们的二分索引指向的值小于左边范围的最小值,那么我们还没有完全在左边的二分查找范围之内,右指针要移动,然后重新二分查找;如果dir等于1,说明我们要在右边的二分范围里面查找,这个时候如果我们的二分索引指向的值大于右边范围的最大值,那么我们还没有完全在右边的二分查找范围之内,左指针要移动。

        while(left <= right){
            ......
            //若还未进入二分查找的区间,则继续二分缩小范围
            if(dir == 0 && nums[mid] < nums[0]){
                right = mid - 1;
                continue;
            }else if(dir == 1 && nums[mid] > nums[nums.length - 1]){
                left = mid + 1;
                continue;
            }
            ......
        }

        如果我们已经完全在我们确定的二分查找范围之内了,就按照正常的二分判断进行查找即可。

        while(left <= right){
            //更新中间值
            mid = (right - left) / 2 + left;
            //如果中间值target
            if(nums[mid] == target){
                return mid;
            }
            ......
            //正常的二分逻辑
            if(nums[mid] < target){
                left = mid + 1;
            }else{
                right = mid - 1;
            }
        }

        最后如果没有找到target,就直接返回-1。

3.代码展示

class Solution {
    public int search(int[] nums, int target) {
        //目标值大于最右边的数,小于最左边的数,找不到
        if(target > nums[nums.length - 1] && target < nums[0]){
            return -1;
        }
        int left = 0;
        int right = nums.length - 1;
        int mid = 0;
        //0代表在最左边进行二分查找,1代表在最右边进行二分查找
        //目标值小于等于最右边的数,在最右边进行二分查找
        //目标值大于最左边的数,在最左边进行二分查找
        int dir = target <= nums[nums.length - 1] ? 1 : 0;
        while(left <= right){
            //更新中间值
            mid = (right - left) / 2 + left;
            //如果中间值target
            if(nums[mid] == target){
                return mid;
            }
            //若还未进入二分查找的区间,则继续二分缩小范围
            if(dir == 0 && nums[mid] < nums[0]){
                right = mid - 1;
                continue;
            }else if(dir == 1 && nums[mid] > nums[nums.length - 1]){
                left = mid + 1;
                continue;
            }
            //正常的二分逻辑
            if(nums[mid] < target){
                left = mid + 1;
            }else{
                right = mid - 1;
            }
        }
        return -1;
    }
}

4.总结

        这道题是一道绕了一下的二分查找问题,有难度的地方就在于我们如果确定并找到target所在的二分查找范围,解决了这一点之后,就变成了一道简单的二分查找问题了。这道题就简单啰嗦到这里,祝大家刷题愉快~

你可能感兴趣的:(Leetcode,算法,leetcode,数据结构,java,二分查找)