关于二分查找的笔记
程序员文章站
2024-03-17 22:30:46
...
简介
做题的过程中,经常要对数组进行二分查找,二分查找主要有两种目的:
- 判断数组中是否有某个元素
- 判断元素在数组中首次出现的位置
本文列出了上述两种目的的代码,以及一些笔记。
判断存在性
判断存在性的代码比较简单,直接贴出来了:
bool
bin_search(vector<int> nums, int target) {
int lo = 0;
int hi = nums.size()-1;
int mid = (lo+hi)/2;
while (lo <= hi) {
if (nums[mid] == target)
return true;
else if (nums[mid] < target) {
lo = mid+1;
} else {
hi = mid-1;
}
mid = (lo+hi)/2;
}
return false;
}
查找位置
这种类型的二分用于查找:
- 如果元素存在,元素的第一次出现位置
- 如果元素不存在,元素应该在的位置
看过《算法:第四版》的同学应该知道,这就是书中的rank方法。我之前看过书中的代码,不过“纸上得来终觉浅,绝知此事要躬行”,在我做题的时候,虽然能写出rank的代码来,思路却总是不顺畅,这里错一点那里错一点。今天终于下定决心总结一下问题所在:
- 变数太多,例如左端点lo什么时候更新,
nums[mid]<target
还是nums[mid]<=target
?lo怎么更新,是lo=mid
还是lo=mid+
?最后的结果是lo
还是hi
,还是要另外判断?这些问题总是缠绕着我本来就不清醒的脑子。 - 情况太多
在经过思考之后,我总结出自己的两个解决方法:
- 一个问题可能有多种解决方式,二分也有多种实现方法,而且这些方法都是一套一套的,一套方法中的各个小步骤往往是固定搭配着用的。为了解决第一个问题,可以先定下一些不变的量,减少变数。
- 首先思考简化情况的方法,如果不行的话,穷尽所有情况。
下面说二分的其他两种实现。
第一种实现
int
find(vector<int> nums, int target) {
int lo = 0;
int hi = nums.size()-1;
// 最终lo会指向这个数应该在的位置
// 那么lo应该指向的是<target的数,最后mid会等于lo,lo需要越过mid
int mid = (lo+hi)/2;
while (lo < hi) {
if (nums[mid] < target) {
lo = mid+1;
} else {
// nums[mid] >= target
hi = mid;
}
mid = (lo+hi)/2;
}
return lo;
}
下面记录一下我的思考过程,不确定是否对别人有帮助。
刚开始时代码模板是:
// 1
int
find(vector<int> nums, int target) {
int lo = 0;
int hi = nums.size()-1;
int mid = (lo+hi)/2;
while () {
mid = (lo+hi)/2;
}
return ?;
}
首先,确定一下变数:最终lo要么指向target出现的第一个位置,要么指向它应该在的位置,于是代码变成了:
// 2
int
find(vector<int> nums, int target) {
int lo = 0;
int hi = nums.size()-1;
int mid = (lo+hi)/2;
while () {
mid = (lo+hi)/2;
}
return lo;
}
现在再确定一下变数:lo指向的数小于target,hi指向的数大于等于target。
这样最终会得到一条界限,于是代码变成:
// 1
int
find(vector<int> nums, int target) {
int lo = 0;
int hi = nums.size()-1;
int mid = (lo+hi)/2;
while () {
if (nums[mid] < target) {
lo = mid; // 由于nums[mid]<target,所以可以保证nums[lo]总是小于target
} else {
hi = mid; // 同理,可以保证nums[hi] >= target
}
mid = (lo+hi)/2;
}
return lo;
}
现在先考虑一下最终的情况:
-
nums[lo]<target; nums[hi]>=target; lo+1==hi;
,这是最希望得到的情况,lo和hi分别在界限两边,此时退出循环对lo+1,然后返回lo即可。 -
nums[lo]>=target; lo==hi;
,有可能整个数组都大于等于target -
nums[hi]<target; lo==hi;
,整个数组都小于target
由上述三种情况,可以写出代码:
int
find(vector<int> nums, int target) {
int lo = 0;
int hi = nums.size()-1;
int mid = (lo+hi)/2;
while (lo != mid) { // 这个条件可以保证在上面三种情况发生时退出
if (nums[mid] < target) {
lo = mid; // 由于nums[mid]<target,所以可以保证nums[lo]总是小于target
} else {
hi = mid; // 同理,可以保证nums[hi] >= target
}
mid = (lo+hi)/2;
}
if (nums[lo]<target) {
// 第1,3种情况
lo+1;
}
return lo; // 隐含第2种情况
}
第二种实现
下面是比较简洁的实现:
int
find(vector<int> nums, int target) {
int lo = 0;
int hi = nums.size()-1;
// 最终lo会指向这个数应该在的位置
// 那么lo应该指向的是<target的数,lo需要越过mid
int mid = (lo+hi)/2;
while (lo < hi) {
if (nums[mid] < target) {
lo = mid+1;
} else {
// nums[mid] >= target
hi = mid;
}
mid = (lo+hi)/2;
}
return lo;
}