写在前面
小伙伴们想问为什么跳过了4,因为要复习不完了!!!(留下了菜狗的泪水 |_|。。。。)
所以就先把接下来的重头戏——二分查找先整理一下,话不多说就开始吧。
关于贪心算法
没啥想说的,因为学过数据结构、算法设计、blablabla等相关课程的小伙伴都听说过贪心,详细的一些事情由于本人很菜肯定没别人整理的好,所幸只挑选出二分查找来一起回顾一下贪心的魅力。
问题:二分查找
题目详情
给定一个有序数组,在其中查找满足条件的某个位置:
(1)出现小于v的第一个数;
(2)出现小于等于v的第一个数;
(3)出现大于v的第一个数;
(4)出现大于等于v的第一个数。
(5)是否存在值等于v的数
分析
话不多说,直接看代码。
首先最简单的问题,判断区间中是否存在值为v的数(注意使用的区间为左闭右开区间)
bool binarySearch(vector<int> data, int value) {
int left = 0;
int right = data.size();
while (left < right) {
int mid = left + (right - left) / 2;
if (data[mid] == value) {
return true;
}
else if (data[mid] < value) {
left = mid + 1;
}
else{
right = mid;
}
}
return false;
}
第二个是找到大于等于v的第一个数
int binarySearch(vector<int> data, int value) {
int left = 0;
int right = data.size();
while (left < right) {
int mid = left + (right - left) / 2;
if (data[mid] < value) {
left = mid + 1;
}
else {
right = mid;
}
}
return left;
}
第三个是找到大于v的第一个数
int binarySearch(vector<int> data, int value) {
int left = 0;
int right = data.size();
while (left < right) {
int mid = left + (right - left) / 2;
if (data[mid] <= value) {
left = mid + 1;
}
else {
right = mid;
}
}
return left;
}
代码其实到这里就不用再写了,细心的小伙伴们已经发现其实无论题目所要求的条件如何,代码的整体逻辑结构是完全相同的,我们来总结一下二分查找的核心部分。
算法核心
(1)首先,要搞清楚,二分查找其实划分为两大类问题,确定上界还是确定下界。比如题目中的1、2都是上界问题,而3、4都是下界问题;
(2)其次,维护的区间到底是什么样子的(如果你精通二分查找可以直接忽略我接下来的叨叨叨)。下界问题一般建议使用左闭右开区间,而上界问题一般建议使用左开右闭区间,按照这样维护区间有利于接下来我们可以更清楚地更新区间左右边界;
(3)接下来就是最关键的问题,判断后更新区间边界。以下界问题举例,更新基本上都是 ,原因就是有左闭右开的区间决定的,当前mid位置的值发生判定后,如果我们需要向右更新区间,那左边界必然要取mid的下一位,但如果我们向左更新区间,根据左闭右开,右边界就需要定位在mid。
问题转化
经过核心分析之后,相信对于算法原理肯定是完全了解了,这时我们还要思考一个问题,难道上界问题与下界问题之间没有联系么?
如图所示,标出了上界问题与下界问题的搜索结果,可以看到所有的上界问题其实都是对应的下界问题的最终搜索位置的前一位,这样我们只需要搞懂一类问题,另外一类问题就迎刃而解了。
类似问题
留给各位自行思考,其实二分查找不一定可以只使用在有序离散序列中,也可以使用在单调函数上
砍木头
将n根长度相同的木头砍成至少m段,要求砍完之后每段木头的长度相等。问砍后每段木头最长有多长?(对于长度进行二分查找)