题目描述:
整数数组 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) 的算法解决此问题。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1
示例 3:
输入: nums = [1], target = 0
输出: -1
提示:
1 <= nums.length <= 5000-10^4 <= nums[i] <= 10^4nums中的每个值都 独一无二- 题目数据保证
nums在预先未知的某个下标上进行了旋转 -10^4 <= target <= 10^4
思路:
首先应该以logn的时间复杂度找到“反转”的位置。二分查找来实现。
找到之后,设计二分查找变种来适应这个旋转的数组。我有印象有个轮转数组的一个技巧,好像是分别反转。但是这里先不用了。
使用三次二分查找:
- 第一次,找到“反转”的点。这里只能用“非标准”二分查找了,因为如果
left=mid+1很可能会越过最大值。 - 第二次,对“前半部分”二分查找,使用标准二分查找即可
- 第三次,对“后半部分”二分查找,同上。
实现:
class Solution {
public int search(int[] nums, int target) {
// find position k
int le = 0, ri = nums.length; // left close, right open
int k;
while (le < ri) {
int mid = (le + ri) >> 1;
if (nums[le] < nums[mid]) {
le = mid; // have to be a variant - otherwise may miss
} else {
ri = mid;
}
}
k = le+1; // exact largest value is le-1, but we want open.
int lpl = 0, lpr = k;
while (lpl < lpr) {
int mid = (lpl + lpr) >> 1;
if (target == nums[mid]) {
return mid;
}
if (target < nums[mid]) {
lpr = mid;
} else {
lpl = mid + 1;
}
}
int rpl = k, rpr = nums.length;
while (rpl < rpr) {
int mid = (rpl + rpr) >> 1;
if (nums[mid] == target) {
return mid;
}
if (target < nums[mid]) {
rpr = mid;
} else {
rpl = mid + 1;
}
}
return -1;
}
}
DS简评:
这段代码的目标是在旋转后的有序数组中查找目标值。其思路分为两步:首先找到旋转点将数组分为两个有序子数组,然后分别在两个子数组中进行二分查找。以下是详细分析:
思路分析
-
寻找旋转点(k):
- 使用二分查找确定旋转点。旋转点将数组分为两个有序部分。例如,数组
[4,5,6,7,0,1,2]的旋转点为索引3(值为7),分割后的子数组为[4,5,6,7]和[0,1,2]。 - 循环逻辑:若中间元素大于左端元素,说明左半部分有序,将左边界右移;否则,旋转点在左半部分,右边界左移。循环结束时,
k设为le + 1,即右半部分的起始位置。
- 使用二分查找确定旋转点。旋转点将数组分为两个有序部分。例如,数组
-
二分查找目标值:
- 分别在左右两个有序子数组中执行标准二分查找。若在任一子数组中找到目标值,返回其索引;否则返回
-1。
- 分别在左右两个有序子数组中执行标准二分查找。若在任一子数组中找到目标值,返回其索引;否则返回
时间复杂度
- 寻找旋转点:使用二分查找,时间复杂度为 O(log n)。
- 两次二分查找:每次查找的时间复杂度均为 O(log n)。
- 总体时间复杂度:三次独立的二分查找,总时间复杂度为 O(log n)。
空间复杂度
- 仅使用固定数量的变量,空间复杂度为 O(1)。
代码亮点
- 高效性:三次二分查找确保对数级别的时间复杂度。
- 鲁棒性:正确处理无旋转、完全旋转等边界情况。
潜在问题
- 重复元素:代码假设数组元素唯一。若存在重复元素,可能导致旋转点判断错误。
- 极端情况:如单元素数组或未旋转数组,均能正确处理。
总结:该算法高效且简洁,适用于无重复元素的旋转有序数组查找问题。