题目:
整数数组 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(logn))
算法步骤:
-
初始化两个指针
left和right分别指向数组的首部和尾部。 -
进行二分查找:
-
计算中间索引
mid = (left + right) // 2。 -
如果
nums[mid] == target,则返回mid。 -
如果
nums[left] <= nums[mid],说明左侧是有序的:- 如果
nums[left] <= target < nums[mid],则target应该在左侧,令right = mid - 1。 - 否则,
target应该在右侧,令left = mid + 1。
- 如果
-
否则,右侧是有序的:
- 如果
nums[mid] < target <= nums[right],则target应该在右侧,令left = mid + 1。 - 否则,
target应该在左侧,令right = mid - 1。
- 如果
-
-
如果找不到
target,返回-1。
时间复杂度和空间复杂度分析:
- 时间复杂度:O(logn),因为我们使用了二分查找。
- 空间复杂度:O(1),只使用了常数个额外的变量。
Q:为什么需要判断 target <mid
在二分查找的这个变种中,我们需要确定目标值 target 在旋转后的数组中的哪个部分(左侧还是右侧)。
当我们找到中间元素 nums[mid],首先我们需要判断数组的哪一侧是有序的。有两种情况:
- 如果 nums[left]≤nums[mid],说明左侧是有序的。
- 否则,右侧是有序的。
对于第一种情况(左侧有序):
- 如果 target 在 nums[left] 和 nums[mid] 之间(即 nums[left]≤target<nums[mid]),那么我们可以确定 targettarget 在左侧。
- 否则,targettarget 在右侧。
类似地,对于第二种情况(右侧有序):
- 如果 targe 在 nums[mid] 和 nums[right] 之间(即 nums[mid]<target≤nums[right]),那么我们可以确定 targettarget 在右侧。
- 否则,targettarget 在左侧。
总结一下,target<midtarget<mid 的判断用于确定 target 是否在有序的一侧内,从而决定下一步搜索的方向。这是因为如果一侧是有序的,我们可以很容易地确定 target 是否在那一侧。
代码:
int search(List<int> nums, int target) {
int left = 0;
int right = nums.length-1;
while (left < right) {
int mid = (left + right) >> 1;
int midValue = nums[mid];
if (midValue == target) {
return mid;
}
if (nums[left] <= nums[mid]) {
if (target < midValue && nums[left] < target) {
right = mid - 1;
} else {
left = mid + 1;
}
} else {
if (target > midValue && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}