33. 搜索旋转排序数组
整数数组
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 。
你必须设计一个时间复杂度为O(log n)
的算法解决此问题。
时间复杂度是
O
(
l
o
g
n
)
O(log n)
O(logn)的查找算法,那显然是采用二分查找的方式,但是旋转后的数组在整体上是无序的,而要采取二分法则必须要求数组有序,但是,如何对旋转后的数组进行排序,那么时间复杂度最低也是
O
(
n
∗
l
o
g
n
)
O(n*log n)
O(n∗logn)不符合题目要求。
观察数组其实不难发现,虽然数组整体是无序的,但是数组旋转后有两个严格单调的区间,可以在这两个区间上进行二分查找。
那么这两个区间的边界处满足什么条件?
旋转后的新数组[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]
,左边的严格单调区间包含nums[k]...nums[n-1]
右边的严格单调区间包含nums[0]...nums[k-1]
显然nums[0]<nums[n-1]
,那么在这两个单调区间的边界i
处有nums[i]<nums[i-1]
。
int border = 0;
for (int i = 1; i < nums.size(); i++) {
if (nums[i] >= nums[i - 1])
continue;
else {
border = i;
break;
}
}
找到边界border
后,就可以在区间[0,border-1]
和[border,n-1]
上进行二分查找了。
int bsearch(vector<int>& nums, int left, int right, int target) {
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] > target)
right = mid - 1;
else if (nums[mid] < target)
left = mid + 1;
else
return mid;
}
return -1;
}
int search(vector<int>& nums, int target) {
int k = 0;
for (int i = 1; i < nums.size(); i++) {
if (nums[i] >= nums[i - 1])
continue;
else {
k = i;
break;
}
}
int in_left = bsearch(nums, 0, k - 1, target),
in_right = bsearch(nums, k, nums.size() - 1, target);
return (in_left < 0 && in_right < 0)
? -1
: (in_left < 0 ? in_right : in_left);
}