题目描述
假设按照升序排序的数组在预先未知的某个点上进行了旋转。搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。算法时间复杂度必须是 O(log n) 级别。
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
解题思路
参考找“旋转数组的最小值”的算法,改进版。(先判断mid指向前后哪一部分)
注意题目中规定了没有重复数字,所以此时只有纯“有序数组”这一种特殊情况。
参考代码
自己提交的“又臭又长”的代码,特判太多,不好。(自己写的代码特判这么多,真是难为自己了)
class Solution {
public:
int search(vector<int>& rotateArray, int target) {
int length = rotateArray.size();
if(length == 0)
return -1;
int low = 0, high = length-1;
while(low <= high){
// 特殊情况:有序数组(low和high落入同一有序区间)
if(rotateArray[low] < rotateArray[high])
return normalBinarySearch(rotateArray, low, high, target);
int mid = (low + high) >> 1;
if(rotateArray[mid] == target)
return mid;
// 这里要加等号,不然[1] 0就通不过
if(rotateArray[mid] >= rotateArray[low]){
if(rotateArray[mid] > target && target > rotateArray[high]) // 注意这里严格的条件限制&& eg: {4,5,6,7,0,1,2}
return normalBinarySearch(rotateArray, low, mid, target);
else
low = mid + 1;
}else if(rotateArray[mid] <= rotateArray[high]){
if(rotateArray[mid] < target && target < rotateArray[low]) // 注意这里严格的条件限制&&
return normalBinarySearch(rotateArray, mid, high, target);
else
high = mid - 1;
}
}
return -1;
}
int normalBinarySearch(vector<int> rotateArray, int low, int high, int target){
while(low <= high){
int mid = (low + high) >> 1;
if(rotateArray[mid] == target)
return mid;
else if(rotateArray[mid] < target)
low = mid + 1;
else
high = mid - 1;
}
return -1;
}
};
以上代码是自己写的,其中还是有很多小细节debug了好几次才通过。 常规思路就是这样。
大佬代码(上面我提交的代码不用看,又臭又长,当时真是难为自己了。看这个就好,推荐!!!)
class Solution {
public:
int search(vector<int>& nums, int target) {
int len = nums.size();
if (len == 0) {
return -1;
}
int left = 0;
int right = len - 1;
while (left < right) {
int mid = left + ((right - left + 1) >> 1); // 取右中位数
// 右半部分有序(首先我们要明确:我们需要在有序区间中进行“具体操作”,其它操作丢给else即可)
if (nums[mid] <= nums[right]) { // 这里这个等号至关重要(记住“旋转数组”题目,判断nums[mid]和两边界的大小时,要加个等号)!! 不加等号就错了,特例:{3, 1} 3
// 使用上取整的中间数,必须在上面的 mid 表达式的括号里 + 1
// 重要:把比较好写的判断(如 target 落在有序区间的那部分)放在 if 的开头考虑,把剩下的情况放在 else 里面。(即 先写好写的条件)
if (nums[mid] <= target && target <= nums[right]) { // 因为右半部分有序,所以可以在这部分区间内进行一些“操作”
// 下一轮搜索区间是 [mid, right]
left = mid; // 这里决定了我们的mid要选“右中位数”
} else {
// 只要上面对了,这里不用思考,可以一下子写出来
right = mid - 1;
}
} else { // 左半部分有序
// [left, mid] 有序,但是为了和上一个 if 有同样的收缩行为,(收缩右边界)
// 我们故意只认为 [left, mid - 1] 有序
// 当区间只有 2 个元素的时候 int mid = (left + right + 1) >>> 1; 一定会取到右边
// 此时 mid - 1 不会越界,就是这么刚刚好
if (nums[left] <= target && target <= nums[mid - 1]) { // 这是大佬写法(还是这么写比较好)
// if (nums[left] <= target && target < nums[mid]) { // 这样写也可以
// 下一轮搜索区间是 [left, mid - 1]
right = mid - 1; // (保证了)和上面同样的收缩行为
} else {
// 同理,只要上面对了,这里不用思考,可以一下子写出来
left = mid;
}
}
}
// 有可能区间内不存在目标元素,因此还需做一次判断
if (nums[left] == target) {
return left;
}
return -1;
}
};
python版本(可以看看,但不是很推荐)
class Solution:
def search(self, nums, target):
size = len(nums)
if size == 0:
return -1
left = 0
right = size - 1
while left < right:
# mid = left + (right - left + 1) // 2
mid = (left + right + 1) >> 1
# 这里的这个if要是不用等号,就要这么写才行: if nums[mid] < nums[left]
# 右半部分有序
if nums[mid] < nums[left]: # 这里改为nums[right]就不对了。 特例:[3,1] 3
if nums[mid] <= target <= nums[right]:
left = mid
else:
right = mid - 1
else: # 上面的if改为nums[right]不对:因为此时无法保证当不满足上述if时,[left, mid]是有序的
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid
# 后处理
return left if nums[left] == target else -1