浅谈二分算法
二分
首先知道一下二分是什么。
二分,是一种快速处理大型数据的方法。基本逻辑是折半查找。
设有一个共有 n n n 个数字的数组,要从中查询某个元素,就可以用二分查找。
注:这里的数组默认其成员数值具有单调性。这个点十分重要。
还记得小时候(我现在才新初一)跟同学玩过一个游戏(数字炸弹),当时玩得可欢了,一直以为是一个概率小游戏(其实确实有概率的成分),每局能玩上十几分钟。
但是现在学完信息学后,特别是学完二分后,对它有了一种快速解决的方法。
首先我们知道,设出题者所想的这个数字为 x x x,则 x ∈ [ 1 , 100 ] x\in[1,100] x∈[1,100],这是我们玩的规则,不过大差不差。
那么很显然,因为 1 ∼ 100 1\sim 100 1∼100 具有单调上升的性质,而我们又只需要查找一个数,暴力的求解方法是从 1 1 1 尝试到 100 100 100,最坏情况下需要枚举 100 100 100 次,十分漫长。
而我们可以想到,每次说出一个数 y y y,只要不是 y = x y=x y=x,就可以排除 100 − y + 1 100-y+1 100−y+1 或 y y y 个数(这取决于你猜的这个数是大了还是小了)。
那么我们就可以利用这个特性,假设共有 n n n 个数字,每次都排除 n ÷ 2 n\div2 n÷2 个数字,这样最多经过 log 2 n \log_2^n log2n 次后就可以结束游戏。
其实小学课本里就有类似的例子,是数学书里一个单元叫做 “ 找次品 ” 的数学问题(不知道的自己查),但那里是用 log 3 n \log _3^n log3n 的时间,这里不再赘述。
双指针
双指针,顾名思义,就是运用两个变量 l , r l,r l,r 表示所求区间的左右端点,可以更加轻松地控制对答案有贡献的区间。
设所求元素为 p p p,序列为 a a a。
比如每次取个中点 m i d = ( l + r ) 2 mid=\frac{(l+r)}{2} mid=2(l+r),若 p < a m i d p<a_{mid} p<amid,则 r ← m i d r\leftarrow mid r←mid,左端点 l l l 不变。将区间控制为 [ l , m i d ] [l,mid] [l,mid]。
否则 l ← m i d + 1 l\leftarrow mid+1 l←mid+1,将区间控制为 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]。
但这里问题就来了,区间为空的判定条件是 l = r l=r l=r 还是 l > r l>r l>r 呢?
二分查找的边界条件
枚举以下情况
- 闭区间
若你用的是闭区间,即 [ l , r ] [l,r] [l,r],则判断条件即为 l = r l=r l=r,因为若 l = r l=r l=r,则 [ l , r ] [l,r] [l,r] 其实就是 [ l , l ] [l,l] [l,l] 或 [ r , r ] [r,r] [r,r] ,就是一个数,答案也显然即为 a l a_l al。
示意图:
- 开区间
开区间,是 ( l , r ) (l,r) (l,r),不包括 l , r l,r l,r 。所以有效区间为 [ l + 1 , r − 1 ] [l+1,r-1] [l+1,r−1]。判断条件即为 l + 1 = r − 1 l+1=r-1 l+1=r−1。
示意图:
- 左开右闭
左开右闭,即 ( l , r ] (l,r] (l,r] 有效区间为 [ l + 1 , r ] [l+1,r] [l+1,r],边界条件即为 l + 1 = r l+1=r l+1=r,还是就剩下一个数为止。
示意图:
- 左闭右开
左闭右开, [ l , r ) [l,r) [l,r),有效区间为 [ l , r − 1 ] [l,r-1] [l,r−1],边界条件为 l = r − 1 l=r-1 l=r−1。
示意图:
所以可以总结一下,不管你的 l , r l,r l,r 都表示什么意思,重点就是四个字,有效区间!
大概大家也注意到了,我在解释除了闭区间其他情况的时候,有效区间的左右端点都是用闭区间来表示,为什么呢?当然是因为我个人喜欢用闭区间啦。