LeetCode 33. 搜索旋转排序数组

72 篇文章 0 订阅
33. 搜索旋转排序数组

难度 中等

整数数组 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
题解
解法一

​ 看到这道题的时候,首先想到的就是二分查找,但是和二分查找不同的是,这里的数组是升序数组的旋转。不能直接进行二分查找,然后相到,那就直接把旋转数组的分界找出来,然后在进行二分查找。

  • 查找分界

    int boundSearch(int[] nums){
        int start = 0;//左指针
        int end = nums.length - 1;//右指针
        int mid = 0;//中指针
        int pre = nums[0];//第一个值
        while(start <= end){//结束条件
            mid = start + ((end - start) / 2);//更新中间指针
            if (nums[mid] == pre){//如果指针不移动了,就是升序的最大值
                return mid;
            }else if(nums[mid] > pre){//如果中值大于第一个值,那么分界在右边
                start = mid;//向右移动左指针
            }else{
                end = mid;//向左移动右指针
            }
            pre = nums[mid];//每次都更新pre为第一个值
        }
        return mid;
    }
    

    ​ 如何进行查找分界呢,也利用二分的思想,查看一下中值是否大于第一个值;如果大于第一个值,那分界在中值右边,更新指针,再往右边找;如果小于第一个值,那么分界在中值左边,更新指针,再往左找。当发现中值等于第一个值,说明指针不移动了,此时指向的值就是最大值。

    ​ 这里说明一下,为什么一定是最大值。

    • 45670123,start = 0,end = 7,pre = nums[0] = 4,mid = (end + start) / 2 = 3,nums[mid] = 7 > pre,会走第二个条件,start = mid = 3,pre = nums[mid] = 7
    • 45670123,start = 3,end = 7,mid = (end + start) / 2 = 5,nums[mid] = 1 < pre,会走第三个条件,end = mid = 5,pre = nums[mid] = 1
    • 45670123,start = 3,end = 5,mid = (end + start) / 2 = 4,nums[mid] = 0 < pre,会走第三个条件,end = mid = 4,pre = nums[mid] = 0
    • 45670123,start = 3,end = 4,mid = (end + start) / 2 = 3,nums[mid] = 7 > pre,会走第三个条件,start = mid = 3,pre = nums[mid] = 7
    • 45670123,start = 3,end = 3,mid = (end + start) / 2 = 3,nums[mid] = 7 = pre,会走第一个条件,返回mid = 3
class Solution {
    public int search(int[] nums, int target) {
        int k = boundSearch(nums);//查找最大值边界
        int ans1 = binSearch(nums, target, 0, k);//查找左边
        int ans2 = binSearch(nums, target, k + 1, nums.length - 1);//查找右边
        if(ans1 != -1){//左边找到
            return ans1;
        }else if(ans2 != -1){//右边找到
            return ans2;
        }
        return -1;//没有找到
    }

    int boundSearch(int[] nums){
        int start = 0;//左指针
        int end = nums.length - 1;//右指针
        int mid = 0;//中指针
        int pre = nums[0];//第一个值
        while(start <= end){//结束条件
            mid = start + ((end - start) / 2);//更新中间指针
            if (nums[mid] == pre){//如果指针不移动了,就是升序的最大值
                return mid;
            }else if(nums[mid] > pre){//如果中值大于第一个值,那么分界在右边
                start = mid;//向右移动左指针
            }else{
                end = mid;//向左移动右指针
            }
            pre = nums[mid];//每次都更新pre为第一个值
        }
        return mid;
    }

    int binSearch(int[] nums, int target, int start, int end){//二分查找
        while(start <= end){
            int mid = start + ((end - start) / 2);
            if(nums[mid] == target){
                return mid;
            }else if(nums[mid] > target){
                end = mid - 1;
            }else{
                start = mid + 1;
            }
        }
        return -1;
    }
}
解法二:官方题解

​ 看到题解的的时候确实意想不到(反正每次都想不到的啦),官方题解还是利用二分到底,但是刚开始看文字解析的时候还不是很懂,看了几遍,才发现,原来是利用有序这个特点进行二分。

​ 对整个数组进行二分的时候,出现三个点,nums[0],nums[mid],nums[nums.length - 1],由于数组有序,那么二分之后,一定在做边或者右边有序,因为只旋转了一次。那就出现两种情况,左边有序,右边有序

  • 左边有序

    ​ 左边有序,可以在左边二分,但是也要判断条件,要找的target在不在左边,否则则对右边查找

  • 右边有序

    ​ 同上,左边有序,可以在右边边二分,但是也要判断条件,要找的target在不在右边,否则则左边查找

  • 有人就有疑问,为什么target不在有序的一遍,则去无需的一遍查找,但是无序的一边不满足二分查找的条件。其实模拟一下就好,当去到无需的一边以后,在根据上面判断左右边那边有序,又是重复的问题,就像分治一样,一直分治成非常小的问题,这里可以一直分治到有序,然后再二分。

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;
        int 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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值