题目如下:
Suppose a sorted array is rotated at some pivot unknown to you beforehand.
(i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2).
You are given a target value to search. If found in the array return its index, otherwise return -1.
You may assume no duplicate exists in the array.
分析如下:
首先注意本题说明了元素是没有重复的。没有重复这个条件非常重要,如果有重复,下面的判断语句就会改写。
参考了一下官网,本题其实是二分查找的变型。
第一种思路,找寻pivot,然后分段进行二分查找,麻烦。
第二种思路,直接进行二分查找,需要比普通的二分查找多写一些判断条件。无需找寻pivot。接下来使用这种做法。
第二种思路的具体分析如下:
求mid,得到A[mid]取值。
如果A[mid]==target,直接返回mid。
否则,如果A[mid]>A[l],说明mid左侧不包含pivot,mid左侧单调递增。
如果target取值范围在左侧,则在左侧进行搜索。
反之,在右侧进行搜索。
否则,说明mid左侧包含pivot,mid右侧单调递增。
如果target取值范围再右侧,则在右侧进行搜索。
反之,在左侧进行搜索。
走完(l<=r)的循环依然没有找到target,则返回-1。
我的代码:
//40ms过大集合
class Solution {
public:
int search(int A[], int n, int target) {
int l=0;
int r=n-1;
while(l<=r){ // 注意是while(l<=r),不是while(l<r),否则{1}中找1测试失败。
int mid=l+(r-l)/2;
if(A[mid]==target){
return mid;
}else if(A[mid]>=A[l]){ //注意是A[mid]>=A[l]不是,A[mid]>A[l],理由和上面同理。最后只有1个元素或者2个元素时,mid和l重合。
if(target<A[mid]&&target>=A[l])
r=mid-1;
else
l=mid+1;
}else {
if(target<=A[r]&&target>A[mid])
l=mid+1;
else
r=mid-1;
}
}
return -1; //循环全部跑完依然找不到就return -1。
}
};
小结:
(1) 看到leetcode官网上还介绍了一个相关问题,找寻pivot,也就是去找寻最小的元素。思路和上面的相似,代码如下。
注意比较有意思的是,这里的while()条件是 while(A[L]>A[R]),而不是上面的while(L<R)。为什么呢?因为要找寻pivot,就需要让L一直在pivot左边的区域运动,R一直在pivot右边的区域运动,并且,在运动过程中,L和R都不断地向pitvot靠拢,最后临界状态就是L=pivot。这个过程中,当然也满足while(L<R)的条件,但是while(L<R)的条件带来的限制比较弱,可能L和R都在pivot左边,也能够满足L<R。本题是需要找到pivot,所以需要一个比上面更强的限制条件。
int FindSortedArrayRotation(int A[], int N)
int L = 0;
int R = N - 1;
while (A[L] > A[R]) {
int M = L + (R - L) / 2;
if (A[M] > A[R])
L = M + 1;
else
R = M;
}
return L;
}
参考资料:
(1) http://leetcode.com/2010/04/searching-element-in-rotated-array.html
update : 2015-01-02
写了个递归版本,非常好理解,直接看下面的注释就可以理解。但是带来的tradeoff是,交了几次都是64ms,比上面的40ms的版本慢。后来发现还是比较山寨的,虽然也正确但是不是最快的。
//64ms 较好理解但是比较山寨
class Solution {
public:
int search(int A[], int target, int start, int end) {
int mid = start + (end - start) / 2;
if (start > end) return -1; //找不到target
if (A[mid] == target) return mid; //找到target直接返回
if ((A[mid] > A[end] ) && (target > A[mid])) { //pivot在mid右侧。
//情况1,如果target比mid大,target一定在mid右侧。
//情况2,如果target比mid小,target可能在mid左侧或者右侧,因此此时target可为mid之外的任何一个。
return search(A, target, mid + 1, end);
} else if ((A[mid] < A[start]) && (target < A[mid])) { //pivot在mid左侧,情况2种,分析类比上面
return search(A, target, start, mid - 1);
} else {
//上面两种情况2, target可能是除了mid之外的任何一个位置。
int a = search(A, target, start, mid - 1);
int b = search(A, target, mid + 1, end);
if (a == -1) return b;
if (b == -1) return a;
return a; //或者return b
}
}
int search(int A[], int n, int target) {
return search (A, target, 0, n - 1);
}
};
如果要更快,还是应该采取上面的官网的做法,水中的鱼这篇blog对这个解法写得非常详细好懂。
自己写了写
//40ms
class Solution {
public:
int search(int A[], int n, int target) {
int start = 0, end = n - 1, mid = -1;
while (start <= end) {
mid = start + (end - start) / 2;
if(A[mid] == target) return mid;
if (A[mid] >= A[start]) {
if (target >= A[start] && target < A[mid])
//NOTE1:是target >= A[start] 而不是 target > A[start], 是 target < A[mid] 而不是 target <= A[mid];
//这里的两个边界条件是不统一的。
end = mid - 1;
else
start = mid + 1;
} else {
if (target <= A[end] && target > A[mid]) // NOTE2: 同NOTE1
start = mid + 1;
else
end = mid - 1;
}
}
return -1;
}
};
本题有 续集