LeetCode 33 搜索旋转排序数组
攻克 “搜索旋转排序数组” 问题:Java 实战解析
在算法与数据结构的探索旅程中,我们常常会遇到各种极具挑战性的问题。“搜索旋转排序数组” 便是其中之一,它巧妙地结合了数组的排序特性与旋转操作,对我们的逻辑思维和编程能力提出了较高要求。通过深入研究并解决这个问题,我们不仅能提升对 Java 语言的运用熟练度,更能深入理解如何在复杂情境下设计高效的算法。
一、问题描述
我们面对的是一个整数数组 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)
的算法来解决此问题。
二、问题分析
2.1 数组特性分析
旋转后的数组虽然整体不再是严格的升序,但它具有一个关键特性:可以看作由两个有序的子数组拼接而成。比如 [4,5,6,7,0,1,2]
,前半部分 [4,5,6,7]
是有序的,后半部分 [0,1,2]
也是有序的。这个特性是我们解决问题的关键突破口,因为它让我们有可能利用类似二分查找的思想来快速定位目标值。
2.2 二分查找的适用性
我们知道二分查找在有序数组中能以 O(log n)
的时间复杂度高效地查找目标值。对于旋转排序数组,我们可以尝试在每次迭代中,通过判断中间元素与两端元素的关系,确定中间元素位于哪个有序子数组中,进而判断目标值可能存在的区间,从而缩小查找范围,实现类似二分查找的效果。
2.3 边界条件处理
在进行二分查找的过程中,需要仔细处理边界条件。例如,当数组长度为 1 时,直接判断该元素是否为目标值即可。当数组长度大于 1 时,要注意在更新左右指针时,确保不会越界,并且能正确地根据中间元素与目标值的比较结果来调整查找区间。
三、代码实现
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
}
// 判断左半部分是否有序
if (nums[left] <= nums[mid]) {
if (nums[left] <= target && target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
}
// 右半部分有序
else {
if (nums[mid] < target && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}
}
四、代码说明
4.1 初始化指针
left
指针初始化为 0,指向数组的起始位置;right
指针初始化为 nums.length - 1
,指向数组的末尾位置。这两个指针用于界定当前的查找区间。
4.2 二分查找循环
通过 while (left <= right)
循环,不断缩小查找区间,直到找到目标值或者确定目标值不存在。在每次循环中,计算中间位置 mid
,使用 left + (right - left) / 2
的方式可以避免 left + right
可能产生的溢出问题。
4.3 目标值判断
如果 nums[mid]
等于 target
,说明找到了目标值,直接返回 mid
。
4.4 左半部分有序情况
当 nums[left] <= nums[mid]
时,表明左半部分数组是有序的。此时,如果 nums[left] <= target && target < nums[mid]
,说明目标值在左半部分有序区间内,将 right
更新为 mid - 1
,缩小查找区间到左半部分;否则,将 left
更新为 mid + 1
,将查找区间移动到右半部分。
4.5 右半部分有序情况
当 nums[left] > nums[mid]
时,说明右半部分数组是有序的。如果 nums[mid] < target && target <= nums[right]
,说明目标值在右半部分有序区间内,将 left
更新为 mid + 1
;否则,将 right
更新为 mid - 1
,缩小查找区间到左半部分。
4.6 未找到目标值
如果循环结束后仍未找到目标值,说明目标值不存在于数组中,返回 -1
。
五、时间和空间复杂度分析
5.1 时间复杂度
在整个查找过程中,每次迭代都将查找区间缩小一半,类似于二分查找。因此,时间复杂度为 O(log n)
,其中 n
是数组 nums
的长度。这满足题目中对时间复杂度的要求。
5.2 空间复杂度
代码中除了输入的数组 nums
外,只使用了几个额外的变量,如 left
、right
、mid
等,这些变量占用的空间是常数级别的。因此,空间复杂度为 O(1)
。
通过对 “搜索旋转排序数组” 问题的深入剖析与 Java 代码实现,我们成功地设计出了高效的算法,不仅解决了问题,还深入理解了时间和空间复杂度的优化。希望这篇文章能为大家在算法学习和实际编程中提供有益的借鉴和帮助。