【二分查找】几种基本题型,你会了吗?

文章目录

    • 1、概述
    • 2、二分查找的原理
    • 3、二分查找实现
      • 3.1 查找指定值
      • 3.2 查找>=target的最小下标
      • 3.3 查找<=target的最大下标
    • 4、二分枚举答案
    • 5、总结

1、概述

在进行诸如在有序数组中查找指定元素时使用搜索算法。搜索算法中除了使用枚举遍历之外,还可以通过二分查找来进行搜索查找。针对一组有序数组,或者可以通过排序方法得到有序数组,需要查找到对应元素或者找到满足某些条件的元素的下标等等,这就需要用到今天的 二分查找算法。在数组中查找指定元素,最坏的情况下目标元素在数组的末尾,因此枚举的方法时间复杂度为 O ( n ) O(n) O(n) 。但是通过今天学习的 二分查找 算法可以将搜索的时间复杂度降到 O ( l o g n ) O(logn) O(logn)

2、二分查找的原理

二分查找,也叫折半查找,每次查找指定元素或者待查项,都是根据条件将搜索范围缩小到一半,然后再在这一半的范围内根据条件,再缩小范围,直至找到需要的元素,或者跳出循环。

3、二分查找实现

二分查找算法是经常使用的时间复杂度为 O ( l o g n ) O(logn) O(logn) 的算法,有时题目中明确限定题解的时间复杂度必须是 O ( l o g n ) O(logn) O(logn) 或者 O ( n l o g n ) O(nlogn) O(nlogn),那么多半就是可以利用二分查找的方法对塞法进行优化。

正因为二叉查找算法被多次使用,所以很多程序语言都将二分查找算法封装好,以方便开发者随时调用。比如 C++中的 binary_search()、upper_bound()、lower_search() ,其中 binary_search() 详解见 3.1 节中的 binarySearch() ,后二者则在 3.2 节中有具体说明。python 中也有封装好的函数,值得一提的是 bisect(ls, x)、bisect_right(ls, x) 和 bisect_left(ls, x) 与上文提到的 C++ 中的库函数功能一一对应。

3.1 查找指定值

// 在有序数组中利用双指针实现二分查找,查找指定元素,如果指向元素不存在则返回 -1 
// binary_search(nums.begin(), nums.end(), target)
int binarySearch(vector<int>& nums, int target) {

	int n = nums.size();
	int l = 0, r = n - 1;
	while (l <= r) {
		// int mid = (l + r) >> 1;      // 为了防止溢出进行如下改进
		int mid = l + ((r - l) >> 1); 	// 注意 + 与 >> 的符号优先级
		if (nums[mid] > target) {
			r = mid - 1;
		}
		else if (nums[mid] < target) {
			l = mid + 1;
		}
		else {
			return mid;
		}
	}

	return -1; // 如果数组中不存在指定元素返回 -1
}

3.2 查找>=target的最小下标

// 在升序数组中查找比target大的最小下标,如果不存在则返回 -1
int getBiggerThanTarget(vector<int>& nums, int target) {

	int n = nums.size();
	int ans = -1;						// 二分查找要考虑到ans初始化情况,可以初始化为-1
	int l = 0, r = n - 1;
	while (l <= r) {
		int mid = l + ((r - l) >> 1);
		if (nums[mid] >= target) {		// 把这里的=去掉就是查找 >target的最小下标值
			ans = mid;
			r = mid - 1;
		}
		else {
			l = mid + 1;
		}
	}
	// 通过判断 ans 是否为-1,可以判断是否存在 >=target的最小下标
	return ans;
}

程序语言:lower_bound(nums.begin(), nums.end(), target)
实现功能:Returns an iterator pointing to the first element in the range [first,last) which does not compare less than val(实现返回第一个大于等于 Target \texttt{Target} Target 的迭代器的功能)
获取元素:通过解引用的方法获得 >=   target \texttt{>= target} >= target 的最小下标可以得到对应的元素
获取下标:通过 lower_bound(nums.begin(),   nums.end(),   target)   -   nums.begin() \texttt{lower\_bound(nums.begin(), nums.end(), target) - nums.begin()} lower_bound(nums.begin(), nums.end(), target) - nums.begin() 来获取下标.

还有一个 \texttt{还有一个} 还有一个

程序语言:upper_bound(nums.begin(), nums.end(), target)
实现功能:Returns an iterator pointing to the first element in the range [first,last) which compares greater than val(查找 >   target \texttt{> target} > target 的最小下标).
获取元素:通过对返回的迭代器解引用可以得到对应的元素,
获取下标:通过 upper_bound(nums.begin(),   nums.end(),   target)   -   nums.begin() \texttt{upper\_bound(nums.begin(), nums.end(), target) - nums.begin()} upper_bound(nums.begin(), nums.end(), target) - nums.begin() 来获取下标.

3.3 查找<=target的最大下标

// 查找比target大的最小下标 1 2 2 3 4 
int getBiggerThanTarget(vector<int>& nums, int target) {

	int n = nums.size();
	int ans = -1;						// 二分查找要考虑到ans初始化情况,可以初始化为-1
	int l = 0, r = n - 1;
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (nums[mid] <= target) {		// 把这里的=去掉就是查找 
			ans = mid;
		    l = mid + 1;
		}
		else {
			r = mid - 1;
		}
	}
	// 通过判断 ans 是否为-1,可以判断是否存在 <=target的最大下标
	return ans;
}

4、二分枚举答案

题目是 CSDN 周赛 27 期的题目。选定一个正整数 X,依次去掉该数字的最后一位直至去掉所有的数位。在整个过程中把所有出现过的数字相加得到一个 sum 值。例如 X=509,进行去位操作中出现的数字依次是 509,50,5,它们的和为 564 。现在给出一个 sum ,请求出对应的X。
正整数sum范围为 1 < = s u m < = 1 0 18 1 <= sum <= 10^{18} 1<=sum<=1018

面对这样的一道题目,根据 sum 的值倒推 X 从无下手。那就正着想,假设现在已经知道 X 的值,根据题意进行模拟就可以轻松得到 sum 值,于是可以想到对所有可能出现的答案一一进行模拟,那么可以推导出 sum 值的那个数就是最终答案了。根据题目中提示的sum的范围可知,答案枚举的范围也就是 [1, 1 0 18 10^{18} 1018],时间复杂度为 O(n) ,n表示可能出现的答案的长度。

优化:由简单的推理可以知道随着 X 数值的增大,sum 数值也是在增大的,那么可以利用二分查找的方法来优化时间复杂度。最终呈现的代码就是二分查找的解法。

long long getX(long long sum) {
	long long l = 1, r = 1e18;
	
	while (l <= r) {
		long long x = l + (r - l) >> 1;
		long long sum0 = x;
		while (x) {
			sum0 += x / 10;
			x /= 10;
		}
		

		if (sum0 > sum) {
			r = mid - 1;
		}
		else if (sum0 < sum) {
			l = mid + 1;
		}
		else {
			return x;
		}
	}
	
	return -1;
}

5、总结

(1)常看常新:无论是学习哪种编程语言的都离不开对数据结构的学习,在边学边用过程中,总会有一些算法,比如二分查找,第一次学习的时候感觉已经学的很顺了,但是在某一次使用解题的过程中会发觉,写的不顺了,逻辑不通了,错误百出了,对于经典算法没有肌肉记忆了,这个时候就是我们回过头再次学习的时候了;
(2)学会查询一些官方说明文档:比如 C++ Reference,Python Reference,这些都是一手资料,比很多经过很多人咀嚼的二手资料更贴近算法的“底层”,因为你很难了解到你参考的二手资料作者的知识水平,再一个就是任何分析题解都是以自己的思考角度分析的,很难与读者的角度契合,总归要回归原始的。

你可能感兴趣的:(算法与数据结构,算法,数据结构)