算法模板-----二分查找

二分查找

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;
	}
}

search-insert-position

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果不在数组中返回按照顺序插入的位置索引。(假设数组中无重复元素)
思路:找到第一个大于等于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;
}

search-a-2d-matrix

编写一个高效算法,判断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;
}

first-bad-version

假设有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.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值