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

本文介绍了二分查找算法的基本原理和实现,包括在有序数组中查找指定元素、大于等于目标值的最小下标以及小于等于目标值的最大下标。通过Python和C++的内置函数展示了二分查找的便捷性,并探讨了如何利用二分查找优化搜索效率。最后,以一个实际问题为例,阐述了如何运用二分查找枚举答案,降低时间复杂度。
摘要由CSDN通过智能技术生成

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) {		// 把这里的=去掉就是查找 <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++ ReferencePython Reference,这些都是一手资料,比很多经过很多人咀嚼的二手资料更贴近算法的“底层”,因为你很难了解到你参考的二手资料作者的知识水平,再一个就是任何分析题解都是以自己的思考角度分析的,很难与读者的角度契合,总归要回归原始的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java编程考试常出现的题型包括以下几种: 1. 字符串操作题:要求学生实现字符串的常见操作,比如字符串的拼接、反转、替换、查找等。此类题目考察学生对字符串的基本掌握程度以及对字符串操作方法的熟练度。 2. 线性表、栈、队列和链表题:考察学生对这些数据结构的理解和熟练应用能力,比如实现这些数据结构基本操作、应用场景分析等。 3. 树结构题:要求学生实现二叉树的遍历、查找等操作,或者实现树的相关算法,比如构建哈夫曼树等。 4. 排序算法题:要求学生实现常见的排序算法,比如冒泡排序、快速排序、堆排序、归并排序等,并分析其时间复杂度。 5. 查找算法题:要求学生实现常见的查找算法,比如二分查找、哈希查找等,并分析其时间复杂度。 6. 分治算法题:要求学生实现常见的分治算法,比如归并排序、快速排序等,并分析其时间复杂度。 以下是一些示例题目及其答案: 1. 字符串操作题: 题目:将一个字符串中所有的空格替换为"%20"。 答案:可以先遍历字符串,记录空格的数量,然后计算出替换后的字符串长度,从后向前进行替换。 2. 线性表题: 题目:实现一个栈,要求支持入栈、出栈和获取栈顶元素的操作。 答案:可以使用数组或链表来实现栈,入栈操作即在栈顶插入元素,出栈操作即从栈顶删除元素,获取栈顶元素操作即返回栈顶元素。 3. 树结构题: 题目:实现一个二叉树的前序遍历。 答案:前序遍历的顺序是根节点、左子树、右子树。可以使用递归或者栈来实现前序遍历。 4. 排序算法题: 题目:实现一个快速排序算法。 答案:快速排序的基本思路是选择一个基准元素,将小于基准元素的数放在其左边,大于基准元素的数放在其右边,然后对左右两部分分别递归进行快速排序。时间复杂度为O(nlogn)。 5. 查找算法题: 题目:实现一个二分查找算法。 答案:二分查找基本思路是将数组分成左右两部分,若查找元素小于中间元素,则在左半部分继续查找,否则在右半部分查找,直到找到或者查找区间为空。时间复杂度为O(logn)。 希望这些信息能够对你有所帮助,祝你考试顺利!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wang_nn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值