目录
一. 摘要
这一周基本都在刷二分法的题,因此在这里做一个总结。二分法是我们耳熟能详的一种算法,其核心思想我认为就是6个字:缩小搜索范围。虽然思想简单,但是在实现的过程中却有很多细节需要注意。本文将对二分算法的原理,实现细节作简要的介绍,然后从leetcode中挑选一道题目来巩固练习。
二. 原理
二分算法的思想很简单,目的是通过缩小搜索范围以达到提高算法效率的目的。以“在数组中搜索一个目标数字”为例:
算法的步骤为:
- 中间的数(1号位置)是不目标值?
- 若不是目标值,则缩小目标区间。若该值比目标值大,则新的搜索范围为2号区域,否则为3号区域。、
- 不断迭代,直到找到目标值为止。
注意:使用二分法之前一定要注意!二分法只能用于有序数组中,因为他原理上是通过判断中间与目标值之间的某种关系来缩小范围,在关系缺失的前提下,算法即会失效。
三. 实现
1.程序的一般思路
我个人总结了写二分法程序的流程,如下图:
同样以”寻找数组中的目标值“为例,分析代码:
2. 细节处理
在写程序的过程中,二分法是很讲究细节的,主要包括以下四个方面。.
a. 左右边界的确定:
搜索区域的双边界一般定义为容器的两端,即:
int left = 0 //左边界
int right = nums.size()-1 //右边界
ps:有的算法喜欢把右边界定义为 nums.size(),如果写这一种做法的话后面循环条件,中心值修改都需要作修改。
b. 循环条件的确定
因为我们在初始化的过程中把左右边界定义为容器的两端,因此循环结束的条件应该是
while(left <= right)
为什么这里的条件是小于等于而不是小于呢? 首先我们需要明白两者的区别:
“小于等于” 对应终止条件是: 当左指针>右指针,例如[4,3]; “小于”
对应的终止条件是:当左指针==右指针 ,例如[4,4];
因此使用循环条件小于时,可能会漏判 [4,4],也就是4这一个元素。
这里我们举一个例子来说明:
按照第一节的程序运行,流程如下图:假设循环条件为小于,那么实际上就不会有round3了(left==right),因此程序也就出错。
小总结:循环条件的选择应该考虑的标准是“有无漏判元素”,在实际操作时可以把右边界的元素设为目标值,在草稿纸上走一遍,就能搞清楚了。
c. 中心值的更新方式
更新中心值有两种方式:
int mid = (right+left)/2
int mid = left+(right-left)/2
这两种方式的区别在于:当left或right都特别大时,两者求和会溢出,因此第一种方式容易出bug,反之第二种方式则无此风险。
d. 左右区间的更新
左右区间的更新规则应该根据题目要求来具体指定。继续”寻找数组中的目标值“为例,该例子中的左/右边界更新规则为left = mid-1
以及right= mid+1
。为什么这里不是left = mid
或者right = mid
呢?愿意有两个:
- 中间元素已经判断过了,所以可以跳过mid索引的位置
- 可能会出现死循环,同样以“寻找数组中的目标值”为例:
当目标值在右边界,且更新规则为left = mid
时,程序在round3之后left的值就永远等于3,不会更新,于是就无法搜索到右边界的位置。
小总结:确定左右边界的跟新规则应该根据题意确定,但要注意有无漏判,在实操时建议在草稿纸上写一些边界的例子来检查。
四:具体例子
本小节将通过leetcode中的一道二分法题目(力扣744)来进一步巩固上述解题思路。
理解题意:要求是比目标字母大的第一个字母,那么可以使用二分法不断缩小搜小范围,直至找到最后一个大于目标字母的元素即可。
输入的字符串为:vector<char> letters
, 目标字母:target
第一步:确定左右边界
int left = 0;
int right = letters.size()-1;
第二步:确定循环条件
while(left <= right)
第三步:确定左右边界的更新规则:
根据题意,可有以下思路:
若letters[mid] > target
则比目标字母大的第一个字母只可能在左边,left = mid-1
;否则right = mid+1
第四步:确定返回值
实际上这是寻找左边界的过程,因此可以return letters[left]
综合代码:
char nextGreatestLetter(vector<char>& letters, char target) {
int left = 0;
int right = letters.size()- 1;
while (left <= right )
{
int mid = (left + right ) / 2;
if (target < letters[mid])
right = mid - 1;
else
left = mid + 1;
}
return letters[l];
}
五. 总结
二分法的解题思路为常规套路+结合题意,常规的套路四步骤是基本框架,万变不离其宗,基本上大部分可用二分法处理的问题都可以用这一套路解决。但是,在不同的场景下应该要根据题意做适当地调整。
六.其他
本文参考于:here
另外,推荐一个github 刷题指南,上面有关于leetcode的题目进行了归类,大家可以适量食用。
本人也是一名初学者,道行不深,如果上文有错漏,欢迎各位指正。本文纯手码字,喜欢的朋友点个赞哈