二分查找
find-first-and-last-position-of-element-in-sorted-array
search-insert-position
search-a-2d-matrix
first-bad-version
find-minimum-in-rotated-sorted-array
find-minimum-in-rotated-sorted-array-ii
search-in-rotated-sorted-array
search-in-rotated-sorted-array-ii
模板
给定一个有序数组和目标值,找到第一次、最后一次、任意一次出现的索引,如果没有出现返回-1.
二分查找四个要点:
- 初始化:start=0,end=len-1;
- 循环退出条件:start+1<end;
- 比较中点和目标值;
- 判断最后两个元素是否符合: A[start]、A[end] ? target.
时间复杂度是O(logn),在有序数组的查找比较常用。
给定一个有序整数数组nums和一个目标值target,搜索target,若存在返回其下标,不存在返回-1。
// 常用模板
int search(vector<int>& nums, int target){
int start = 0, end = nums.size()-1;
while(start + 1 < end){
int mid = start + (end - start) /2;
if(nums[mid] == target){
end = mid;
}else if(nums[mid] < target){
start = mid;
}else{
end = mid;
}
}
if(nums[start] == target) return start;
if(nums[end] == target) return end;
return -1;
}
遇到实际问题,对这个模板进行适当修改,大部分题目都可使用。下面有一些其他模板,大部分场景使用模板三,而且还能找到第一次/最后一次出现的位置。
常见题型
find-first-and-last-position-of-element-in-sorted-array
给定一个按照升序排列的整数数组和一个目标值,找出目标值在数组中的开始位置和结束位置。
思路:用两次二分查找分别找第一次和最后一次的位置索引
vector<int> searchRange(vector<int>& nums, int target){
vector<int> res{-1, -1};
if(nums.empty()) return res;
int begin = 0, end = nums.size()-1;
while(begin + 1 < end){
int mid = begin + (end - begin) / 2;
if(nums[mid] < target){
begin = mid;
}else if(nums[mid] > target){
end = mid;
}else{
// 如果相等,继续向左找,能找到第一个目标值的位置
end = mid;
}
}
// 搜索左边的索引
if(nums[begin] == target){
res[0] = begin;
}else if(nums[end] == target){
res[0] = end;
}else{
return res;
}
begin = 0;
end = nums.size() - 1;
while(begin + 1 < end){
int mid = begin + (end - begin) / 2;
if(nums[mid] < target){
begin = end;
}else if(nums[mid] > target){
end = mid;
}else{
//如果相等 继续向右找,能找到最后一个目标值的位置
begin = mid;
}
}
// 搜索右边的索引
if(nums[end] == target){
res[1] = end;
}else if(nums[begin] == target){
res[1] = begin;
}else{
return res;
}
}
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果不在数组中返回按照顺序插入的位置索引。(假设数组中无重复元素)
思路:找到第一个大于等于target的位置
int searchInsert(vector<int>& nums, int target){
if(nums.empty()) return 0;
int begin = 0, end = nums.size() - 1;
while(begin + 1 < end){
int mid = begin + (end - begin) / 2;
if(nums[mid] > target){
end = mid;
}else if(nums[mid] < target){
begin = mid;
}else{
begin = mid;
}
}
if(nums[begin] >= target){
return begin;
}else if(nums[end] >= target){
return end;
}else if(nums[end] < target){
return end + 1; //目标值比所有值都大
}
return 0;
}
编写一个高效算法,判断mxn中,是否存在一个目标值。
该矩阵的特点:
- 每行中整数从左到右按升序排序
- 每行的第一个整数大于前一个行的最后一个整数
思路:将2维的数组转化为1维数组,进行二分搜索。按行展开就是一个升序数组
bool searchMatrix(vector<vector<int>>& matrix, int target){
if(matrix.size() == 0 || matrix[0].size() == 0) return false;
int row = matrix.size(), col = matrix[0].size();
int begin = 0, end = row * col - 1;
while(begin + 1 < end){
int mid = begin + (end - begin) / 2;
int val = matrix[mid/col][mid%col]; // 转化为二维数组对应的值
if(val < target){
begin = mid;
}else if(val > target){
end = mid;
}else{
return true;
}
}
if(matrix[begin/col][begin%col] == target || matrix[end/col][end%col] == target){
return true;
}
return false;
}
假设有n个版本,找出导致之后所有版本出错的第一个错误的版本,可以通过调用bool isBadVersion(version)接口来判断版本号version是否在单元测试中出错。实现一个函数来查找第一个出错的版本,尽可能减少API的调用次数。
int firstBadVersion(int n){
int begin = 0, end = n;
while(begin + 1 < end){
int mid = begin + (end - begin) /2;
if(isBadVersion(mid)){
end = mid;
}else{
begin = mid;
}
}
if(isBadVersion(begin)) return begin;
return end;
}
find-minimum-in-rotated-sorted-array
假设按照升序排序的数组在预先未知的某个点上进行了旋转。(如:[0 1 2 3 4 5]变为[4 5 0 1 2 3]请找出其中最小的元素。(不包含重复元素)
思路:最后一个值作为target,然后往左移动,最后比较start、end的值
int findMin(vector<int>& nums){
if(nums.size() == 0) return -1;
int begin = 0, end = nums.size()-1;
while(begin + 1 < end){
int mid = begin + (end - begin)/2;
if(nums[mid] <= nums[end]){
// 右半边
end = mid;
}else{
// 左半边
begin = mid;
}
}
if(nums[begin] > nums[end]){
return nums[end];
}
return nums[begin];
}
find-minimum-in-rotated-sorted-array-ii
假设按照升序排序的数组在预先未知的某个点上进行了旋转。(如:[0 1 2 3 4 5]变为[4 5 0 1 2 3]请找出其中最小的元素。(包含重复元素)
思路:和上题思路类似,只是需要跳过重复的元素。
int findMin(vector<int>& nums){
if(nums.size() == 0) return -1;
int begin = 0, end = nums.size()-1;
while(begin + 1 < end){
// 去除重复元素
while(begin < end && nums[end] == nums[end-1]) end--;
while(begin < end && nums[begin] == nums[begin+1]) begin++;
int mid = begin + (end - begin) /2;
if(nums[mid] <= nums[end]){
end = mid;
}else{
begin = mid;
}
}
if(nums[begin] >= nums[end]) return nums[end];
return nums[begin];
}
search-in-rotated-sorted-array
假设按照升序排序的数组,在某点旋转数组,搜索一个给定的目标值,如果数组中有这个值返回其下标,如果不存在返回-1。(不包含重复元素)
思路:两条上升直线,四种情况。面试的时候可以画图说明
int search(vector<int>& nums, int target){
if(nums.empty()) return -1;
int begin = 0, end = nums.size()-1;
while(begin + 1 < end){
int mid = begin + (end - begin) /2.0;
if(nums[mid] == target) return mid;
// 判断在哪个区间
if(nums[begin] < nums[mid]){
if(nums[begin] <= target && target <= nums[mid]){
end = mid;
}else{
begin = mid;
}
}else if(nums[end] > nums[mid]){
if(nums[end] >= target && target >= nums[mid]){
begin = mid;
}else{
end = mid;
}
}
}
if(nums[begin] == target) return begin;
else if(nums[end] == target) return end;
return -1;
}
search-in-rotated-sorted-array-ii
假设按照升序排序的数组,在某点旋转数组,搜索一个给定的目标值,如果数组中有这个值返回true,如果不存在返回false。(包含重复元素)
bool search(vector<int>& nums, int target){
if(nums.empty()) return false;
//和上题相比需要处理重复元素
int begin = 0, end = nums.size()-1;
while(begin + 1 < end){
// 处理重复元素
while(begin < end && nums[begin] == nums[begin+1]) begin++;
while(begin < end && nums[end] == nums[end-1]) end--;
int mid = begin + (end - begin) / 2;
if(nums[mid] == target) return true;
//判断在哪个区间
if(nums[begin] < nums[mid]){
if(nums[begin] <= target && target <= nums[mid]){
end = mid;
}else{
begin = mid;
}
}else if(nums[end] >= nums[mid]){
if(nums[end] >= target && target >= nums[mid]){
begin = mid;
}else{
end = mid;
}
}
}
if(nums[begin] == target || nums[end] == target) return begin;
return false;
}
二分搜索在面试中很常见,再次总结下要点:
- 初始化:start=0,end=len-1;
- 循环退出条件:start+1<end;
- 比较中点和目标值;
- 判断最后两个元素是否符合: A[start]、A[end] ? target.