33. 搜索旋转排序数组
题目描述
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -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
算法
说实话,看到评论中有人在讨论这题的意义何在,接着给出了遍历寻找的O(n)复杂度的3行代码,下面还有6、7个人点赞,我是十分无语的。
题目既然给出算法时间复杂度必须是 O(log n) 级别,且原数组是有序变形而来,那么能想到的第一解决方案是二分查找。
二分查找
前提:数组从小到大升序
"""left\right分别为数组nums的首末指针, target是要寻找的数"""
while (left <= right):
mid = (left + right) / 2
if (nums[mid] == target):
return mid
if (nums[mid] > target):
right = mid - 1
else
left = mid + 1
# 如果跳出循环还没有找到这个数,说明数组中没有target,返回-1
return -1;
目前的数组nums并不是从小到大排好序的,而是间断性的有序,如4,5,6,7,0,1,2。这里仍可以借鉴二分查找的思想,只不过对mid要增加些许判断条件
原数组有序的情况下是0,1,2,3,4,5,6,7,旋转过后分为几种情况,即小 大 小、大 小 大的两种形式,对应位置为letf\mid\right这3个指针。小到大还是正常从小到大的顺序,在这一区域中仍可以用正常的二分思想确定left和right。在另一边大到小的乱序中,这需要根据nums[mid]和边界值的判断来确定left和right。详细注释给在代码中
代码
class Solution {
public:
int search(vector<int>& nums, int target) {
/*** 利用二分查找求解问题 ***/
// 三个指针
int left, right, mid;
// 赋初值
left = 0, right = nums.size() - 1;
// 循环开始
while (left <= right)
{
// 计算中间位置的下标
mid = (left + right) / 2;
// 分别判断3个指针是否有目标值
if (nums[mid] == target)
return mid;
if (nums[left] == target)
return left;
if (nums[right] == target)
return right;
// 如果当前判断的这段数组满足nums[left] < nums[right],那么说明这段数组的顺序是从小到大排序的,形如0,1,2,3,4中,0 < 4是成立的
if (nums[left] < nums[right])
{
// 对于正常从小到大排序的数组二分查找
if(nums[mid] > target)
right = mid - 1;
else
left = mid + 1;
}
// 如果这段数组满足nums[left] > nums[right],那么说明这段数组的顺序并不是完全从小到大排序的,形如3,4,0,1,2中,3 > 2是成立的
else
{
if (nums[mid] < nums[right])
{
// 大 小 大,小到大这一段是正常从小到大排序的,如果target处于这一段,意味着下一次的判断需要移到这一段来寻找
if (target > nums[mid] && target < nums[right])
left = mid + 1;
else
// 否则,往左边段寻找
right = mid - 1;
}
else
{
// 小 大 小,同理,小到大是正常排序的,如果target处于这一段,意味着下一次的判断需要移到这一段来寻找
if (target < nums[mid] && target > nums[left])
right = mid - 1;
else
// 否则,往右边段寻找
left = mid + 1;
}
}
}
// 前面都没能返回直至跳出循环说明要寻找的数并不在数组中,返回-1
return -1;
}
};