33. 搜索旋转排序数组
1、题目
整数数组 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
-104 <= nums[i] <= 104
nums
中的每个值都 独一无二- 题目数据保证
nums
在预先未知的某个下标上进行了旋转 -104 <= target <= 104
- 数组
- 二分查找
2、题目分析
(核心) 定理一:只要在顺序区间内,就可以通过区间两端的数值判断 target 是否在其中。
定理二:判断顺序区间还是乱序区间,只需要对比 left 和 right 是否是顺序对即可,left <= right,顺序区间,否则乱序区间。
定理三:每次二分都会至少存在一个顺序区间。
通过不断的用Mid二分,根据定理二,将整个数组划分成顺序区间和乱序区间,然后利用定理一判断target是否在顺序区间,如果在顺序区间,下次循环就直接取顺序区间,如果不在,那么下次循环就取乱序区间。
3、复杂度最优解代码示例
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
// 踩坑:除2这里,位运算是右移1位,而不是2位
int mid = (left + right) >> 1;
if (nums[mid] == target) {
return mid;
}
if (nums[left] <= nums[mid]) {
// left 到 mid 是顺序区间
if (target >= nums[left] && target < nums[mid]) {
// 通过区间两端的数值判断target是否在其中,只有在顺序区间内才可以。而现在顺序区间就是 left 到 mid。区间2端数值为什么是左闭右开,即不包含mid所指数据?因为循环开头,已经判断了 nums[mid] == target。
// 判断target是否在顺序区间,如果在顺序区间,下次循环就直接取顺序区间
right = mid - 1;
} else {
// 如果不在顺序区间,那么下次循环就取乱序区间。
left = mid + 1;
}
}
else {
// mid 到 right 是顺序区间
if (target > nums[mid] && target <= nums[right]) {
// 只有在顺序区间内才可以通过区间两端的数值判断target是否在其中
// 判断target是否在顺序区间,如果在顺序区间,下次循环就直接取顺序区间
left = mid + 1;
} else {
// 如果不在顺序区间,那么下次循环就取乱序区间。
right = mid - 1;
}
}
}
return -1;
}
4、适用场景
旋转排序数组(Rotated Sorted Array)是指一个原本有序的数组,由于某种原因被分成了两部分,其中一部分的元素比另一部分的元素大,两部分各自内部仍然保持有序。搜索旋转排序数组通常涉及寻找某个特定元素在数组中的位置,或者找到数组中的最小元素。以下是一些适用场景:
-
数据恢复:在数据恢复场景中,可能需要从损坏的数据文件中提取信息,这些文件可能因为某些操作导致数据的顺序被打乱,形成了旋转排序数组。
-
算法竞赛和面试:搜索旋转排序数组是一个常见的算法问题,经常出现在算法竞赛和编程面试中,考察候选人对二分查找算法的理解和灵活应用能力。
-
数据库查询优化:在数据库中,如果索引因为某些操作(如大量数据的插入或删除)而变得不再连续,可能需要在旋转排序数组中进行查找以优化查询。
-
文件系统管理:在文件系统中,文件列表可能因为大量文件的移动和删除而变得不连续,搜索旋转排序数组可以帮助快速定位特定文件。
-
股票市场分析:在股票市场分析中,历史价格数据可能会因为时间窗口的变换而形成旋转排序数组,搜索这样的数组可以帮助分析师快速找到特定时间段内的股票价格。
-
图像处理:在图像处理中,可能需要在一组经过旋转排序的像素值中进行搜索,以找到特定的颜色或亮度值。
-
游戏开发:在游戏中,排行榜或者分数列表可能因为玩家的加入和退出而变得不连续,搜索旋转排序数组可以帮助快速找到玩家的排名。
-
生物信息学:在基因序列分析中,可能需要在一组旋转排序的DNA序列中搜索特定的模式或序列。
-
机器学习特征选择:在机器学习中,特征选择可能需要在一组旋转排序的特征值中搜索最优的特征分割点。
-
实时系统:在实时系统中,如果数据流因为时间戳的变化而被分割成多个部分,搜索旋转排序数组可以帮助快速定位最新的数据点。
在这些场景中,使用二分查找的变种来搜索旋转排序数组是非常有效的,因为它可以在O(log n)的时间复杂度内找到目标元素,大大提高了搜索效率。