题目描述
解法
这是局部有序的二分查找,类似的题还有 33. 搜索旋转排序数组 和 81. 搜索旋转排序数组 II,再加上这一题,他们是难度是依次递增的,所以我直接拿这一题记录一下吧
- 首先要知道,我们随便选择一个点,将数组分为前后两部分,其中一部分一定是有序的。
- 我们可以先找出 mid,然后根据 mid 来判断,mid 是在有序的部分还是无序的部分
- 假如 mid 小于 start,则 mid 一定在右边有序部分,即 [mid,end] 部分有序。假如 mid 大于 start,则 mid 一定在左边有序部分,即 [start,mid]部分有序。这是这类题目的突破口。
- 然后我们继续判断 target 在哪一部分, 就可以舍弃另一部分,而这只需要比较 target 和有序部分的边界关系就行了。
- 比如 mid 在右侧有序部分,即[mid,end] 有序。那么我们只需要判断 target >= mid && target <= end 就能知道 target 在右侧有序部分,我们就 可以舍弃左边部分了(通过 start = mid + 1 实现), 反之亦然。
- 接下来,我们考虑重复元素的问题。如果存在重复数字,就可能会发生 nums[mid] == nums[start] 了,比如 30333 。这个时候可以让 start ++,跳过重复项
33题是没有重复项,81题是加了重复项,而本题是在有重复项的基础上再求索引值最小的一个,返回索引值。
- 那我们就要先判断,当左边的索引符合时,说明已经找到索引值最小的,直接返回即可。
- 若左边索引不符合,开始做二分,如果mid符合,mid左边可能还有符合的,要找最小,这时候就更新右边界为mid
- mid不符合的时候就跟88题一样了,判断区间,做二分查找
class Solution {
public:
int search(vector<int>& arr, int target) {
int len = arr.size();
if(len == 0) return -1;
if(len == 1) return arr[0] == target ? 0 : -1;
int left = 0, right = len - 1;
while(left <= right)
{
//最左即是索引值最小的,直接返回
if(arr[left] == target) return left;
int mid = (left + right) / 2;
//找到mid符合,但可能mid左边还有更小的,所以更新right = mid继续找
if(arr[mid] == target) right = mid;
//左边有序
else if(arr[left] < arr[mid])
{
if(arr[left] <= target && target < arr[mid])
right = mid - 1;
else
left = mid + 1;
}
//右边有序
else if(arr[left] > arr[mid])
{
if(arr[mid] < target && target <= arr[right])
left = mid + 1;
else
right = mid - 1;
}
else //arr[left] == arr[mid],left++ 跳过重复项
{
left++;
}
}
return -1;
}
};
除此之外还有二维数组的二分查找,比如 74. 搜索二维矩阵 和 240. 搜索二维矩阵 II,并且惊讶发现这两题的代码完全一样,都能AC
思路在代码的注释里
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int n = matrix.size(), m = matrix[0].size();
if(n == 0 || m == 0) return false;
//选择矩阵左下角作为起始元素
int l = n - 1, r = 0;//从左下角往右上角找
while(l >= 0 && r < m)
{
if(matrix[l][r] == target) return true;
if(matrix[l][r] < target) r++;//右移
else l--;//上移
}
return false;
}
};