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

Posted 栋次大次

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法模板-----二分查找相关的知识,希望对你有一定的参考价值。

二分查找

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.

以上是关于算法模板-----二分查找的主要内容,如果未能解决你的问题,请参考以下文章

算法练习(二分查找/排序)

学习数据结构笔记(15) --- [二分查找算法(非递归)]

二分算法(java超详细)

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

算法套路:二分查找

算法二分法 ② ( 排序数组中查找目标值 | 二分法的经典写法 | 在排序数组中查找元素的最后一个位置 | 二分法的通用模板 )