以下内容来自SidneySun的ACM公开课
二分引入
二分法,我们大部分是从以下类似的算法题认识到的。
# 从有序int数组 li 中找出数字7的索引
li = [1, 3, 4, 5, 7, 8]
这里的二分,是在一种有序数组中找到某一个特定元素的算法:每次将空间分成两份,然后选择其中的一份继续求解。这样求解空间会不断的减半,直到找到特定的元素
二分的本质
那二分的本质是什么呢,单调有序吗?并不是的。
下面一道算法题,可以思考一下:
给定一个长度为n的数组,左边全是奇数,右边全是偶数,试求最后一个奇数的索引(保证第一个数字一定是奇数)
例如数组 li = [3, 5, 1, 7, 11, 9, 2, 4, 10, 6, 8]
最后一个奇数的索引为奇数9所在的索引5
上面的算法题,是否可以用二分呢?
先取两个索引,left = 0, right = len(li) = 10,二分取中间值 mid = (left + right) // 2,得到 mid = 5, li[5] 为 9,奇数,由此判断,索引5前面的数字肯定都是奇数,将 left 赋值为 5,再取中间值 mid = (5 + 10) // 2 = 7,索引 7 的值为 4,偶数,由此判断,索引7后面的数字全为偶数,将right重新赋值为 7 ,以此类推,我们最后得到 left = 5, right = 6,最后一个奇数的索引值即为5
所以,我们可以发现,二分的本质,是取到一个分界点,可以确定该分界点的某侧有一个共有特性,然后可以根据这个分界点,跳过某些数据的处理,达到迅速便捷的效果
二分中会存在一个整数m,使得
- 当 i ≤ m 时,f(i)均为真(假)
- 或当 i ≥ m 时,f(i)均为假(真)
我们可以根据这个性质找到分界点 m
例题
Letcode 455.分发饼干
LetCode链接
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
示例1
输入: g = [1,2,3], s = [1,1]
输出: 1
解释:
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
这道题是否有二分性质呢?可以思考一下,如果我能喂饱4个孩子,那我是否能喂饱3个孩子呢?或者,如果我连3个孩子都喂不饱,那我有可能去喂饱4个孩子吗?
相当于,当 i = 4 时,f(i)判断为真,那小于等于 4 的数字都判定为真;当 j = 3 时,f(i)判断为假,那大于等于3的数字也都判断为假
这样,我们就可以将问题转换为是否存在一个方案能喂饱 x 个孩子
采用二分法,得到 mid ,判断能否用最大的 mid 个饼干喂饱饭量最小的 mid 个孩子;若可以喂饱,继续获取更大的 mid 值进行判断能否喂饱。以此类推,我们能得到一个分界点值,即是我们要找的值
Letcode 209.长度最小的子数组
LetCode链接
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
示例
输入:s = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
同样可以思考,这道题是否有二分性质呢。若我们能找到一个长度为 3 的子数组,其和大于 7,那肯定能找到长度为 4 的子数组的和大于 7;同理,若所有长度为4的子数组的和都不大于7,那我们知道长度小于 4 的所有子数组的和,都不可能大于7了。
所以,这道题,同样是可以用二分法来解决的。PS:这里需要用到前缀和,计算出列表所有前缀和,能更轻松的计算每个子数组的和。
li = [2, 3, 5, 1, 4, 9]
所有前缀和:
s0 = li[0] = 2
s1 = s0 + li[1] = 2 + 3 = 5
s2 = s1 + li[2] = 5 + 5 = 10
s3 = s2 + li[3] = 10 + 1 = 11
.
.
.
若要求索引1到3的子数组[3, 5, 1],只需要用 s3 - s0 = 11 - 2 = 9即可