原题链接Search in Rotated Sorted Array
一个无重复项的递增序列右移了一定距离,在右移后的序列中查找是否有某个元素
比如[0,1,2,4,5,6,7]
右移4位变为[4,5,6,7,0,1,2]
,在[4,5,6,7,0,1,2]
中查找某个元素,事先不知道移动了多少位
对于有序序列,首先可以想到的是利用二分法查找,但是序列别移动后的起点不再是最左边的位置。比如说上面的例子,起点0在下标为4的位置,所以不一定满足nums[0] < nums[n - 1]
,不能直接使用二分法。
但是因为是整体移动,局部仍然是有序的,所以如果确定了某个区间是有序的,那么还是可以使用二分法的
下面的*代表起点
o * o o o o o
大 小 中
left middle right
如果起点在middle的左侧,那么nums[left],nums[middle],nums[right]的大小关系如上
可通过nums[middle] < nums[right]判断起点在middle左边
o o o o o * o
中 大 小
left middle right
如果起点在middle的右侧,那么nums[left],nums[middle],nums[right]的大小关系如上
可通过nums[middle] > nums[right]判断起点在middle右边
以第一种情况为例(起点在middle
左侧)即nums[middle] < nums[right]
如果目标元素在[middle, right]
区间,那么一定有nums[target] >= nums[middle] && nums[target] <= nums[right]
否则,目标元素在[left, middle]
区间内
因为[middle, right]
一定是递增的,所以可以判断目标元素是否在这个区间内,而[left, middle]
区间不是递增的,故不能判断。
第二种情况同理
代码如下
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while(left < right)
{
int middle = (left + right) / 2;
if(nums[middle] == target)
return middle;
/* 第一种情况,起点在middle左边 */
if(nums[middle] < nums[right])
{
/* 目标在[middle, right]区间,因为nums[middle]已经比较过了,所以middle不需要等号 */
if(target > nums[middle] && target <= nums[right])
left = middle + 1;
else
right = middle - 1;
}
/* 第二种情况,起点在middle右边 */
else
{
/* 目标在[left, middle]区间,middle不需要等号,原因同上 */
if(target < nums[middle] && target >= nums[left])
right = middle - 1;
else
left = middle + 1;
}
}
return left < nums.size() && nums[left] == target ? left : -1;
}
};
扩展
原题链接Search in Rotated Sorted Array II
要求和上面的一样,只是所给序列可能带有重复项。
此时情况就不仅仅是上面两种,比如
o o o o o * o
小 小 大 小
left middle right
如果起点在middle的右侧,那么有可能nums[middle] == nums[right]
以[3,1,1]
中找3为例
此时nums[middle] == nums[right]
,那么通过上面的方法判断得知起点在右边(进入else),但是此时[left, middle]
不是递增的。而上面的方法必须保证
[left, middle]
是递增的[middle, right]
是递增的解决办法是通过比较nuns[middle] < nums[right]
判断起点在左边,比较nums[middle] > nums[right]
判断起点在右边,否则有重复项,将right
左移,重新判断。
为什么左移right
因为nums[middle] == nums[right]
,可知存在重复项,那么无法保证[left, middle]
是递增的,所以缩小范围,将区间变为[left, right-1]
,尝试去掉重复项。
代码如下
class Solution {
public:
bool search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while(left < right)
{
int middle = (left + right) / 2;
if(nums[middle] == target)
return true;
/* 起点在左边 */
if(nums[middle] < nums[right])
{
if(target > nums[middle] && target <= nums[right])
left = middle + 1;
else
right = middle - 1;
}
/* 起点在右边 */
else if(nums[middle] > nums[right])
{
if(target < nums[middle] && target >= nums[left])
right = middle - 1;
else
left = middle + 1;
}
/* 有重复项 */
else
{
--right;
}
}
return left < nums.size() && nums[left] == target ? true : false;
}
};