一.题目描述
NC48在旋转过的有序数组中寻找目标值
题目链接:
https://www.nowcoder.com/practice/87c0e7abcbda41e7963660fa7d020995?tpId=196&&tqId=37078&rp=1&ru=/activity/oj&qru=/ta/job-code-total/question-ranking
给出一个转动过的有序数组,你事先不知道该数组转动了多少,在数组中搜索给出的目标值,如果能在数组中找到,返回它的索引,否则返回-1。假设数组中不存在重复项。
(例如:0 1 2 4 5 6 7可能变为4 5 6 7 0 1 2).
二.算法
首先,我们需要求出蓝色分界线的下标x。一旦求出分界点,我们就知道了两段有序数组的分界点,就可以对某一段进行二分查找。那么对于这两段区间,你会发现一个性质:
左边区间内所有的点都大于等于nums[0],而右边区间内所有的点都小于nums[0]。所以,我们可以这样找到分界点x:
int n=nums.size(); int l=0,r=n-1; while (l<r){ int mid=l+r+1>>1;//加法的优先级高于位运算 if (nums[mid]>=nums[0]) l=mid; else r=mid-1; }
接下来的一个问题就是,我们如何确定应该在哪段升序区间进行二分查找?
令目标值target与nums[0]进行比较,若大于等于,说明是左边的,否则右边:
if (target >= nums[0]){ l=0;//左边 } else { l=r+1;//右边 r=n-1; }
最后的问题就是在一个升序的序列中找一个目标值,下面是完整代码:
class Solution { public: int search(vector<int> nums, int target) { int n=nums.size(); if (n==0) return -1;//要是一个空集 就直接返回-1 int l=0, r=n-1; while (l<r){ int mid = (l+r+1)>> 1; if (nums[mid] >= nums[0]) l=mid; else r = mid - 1; } //确定升序区间 if (target >= nums[0]){ l = 0;//左边 } else { l=r+1;//右边 r=n-1; } //在升序序列中寻找target while (l<r){ int mid =(l+r)>> 1; if (nums[mid]>=target) r=mid; else l=mid+1; } if(nums[r]==target){//升序序列中有目标值 return r; } else { return -1; } } };
时间复杂度:o(logn)
空间复杂:o(1) 不需要开辟额外空间
优缺点:思路简单,但是写起来复杂
三.算法
有序数组中查找一般使用二分查找,但是这道题进行了转动。重点在于左右边界的确定,将整个旋转数组分为两部分一定有一部分有序,那么通过判断左边还是右边有序分为两种情况,然后再判断向左走还是向右走。
相比于有序的二分查找多一些判定条件:
(1)要么前半段有序,要么后半段有序。
(2)当前半段有序时:即循环数组中间值比循环数组最左边值大 则 nums[left]<nums[mid]当target比中间值小,比最左值(前半段最小值)大时,肯定在前面部分则right=mid-1 。如果不在前半段,可能在后半段,则right=mid+1。
(3)同理后半段也一样。
class Solution { public: int search(vector<int> nums, int target) { int left=0,right=nums.size()-1; while(left<=right){ int mid=left+(right-left)/2; //防止相加产生溢出 if(nums[mid]==target){ return mid; } else if(nums[left]<=nums[mid]){ //如果左边有序 //在左边有序范围内 if(target>=nums[left]&&target<=nums[mid]){ right=mid-1; } else { left=mid+1;//不在有序范围内,只能去右边找 } } else{ //右边有序 //在右边有序范围内 if(target>=nums[mid]&&target<=nums[right]){ left=mid+1; //不在有序范围内,只能去左边找 } else { right=mid-1; } } } return -1; } };
时间复杂度:o(logn)
空间复杂度:o(1)
优缺点:一个二分就可以解决,但是实现起来比较复杂