vlambda博客
学习文章列表

二分查找常见笔试面试题

讲了二分查找的实现方式和注意细节,本文接着讲二分查找在笔试面试题中的常见题型和实用技巧。


1.最大值最小(最小值最大)

这类题都有明显的关键字特征,求”最小的最大值“,”最大的最小值“,可以考虑用二分来做。


下面给出模板题:


给定一个长度为n的正整数数组 a,将其分成连续的 m 段,使得每段和的最大值最小,输出该最小的最大值。

数据范围:1 < m <= n < 1e5, 0 < ai < 10000

样例:

输入:

5 3

4 2 4 5 1

输出:

6


我们实现一个 check 函数,功能是”判断数组能否被分成 m 段,其中至少有一段的和大于等于 x“。

显然,我们要求的答案就是最后一个满足 check 函数的 x 的值。就变成了上一篇文章的。


现在关键就在于,如何高效地实现 check 函数?


可以贪心地考虑:我们把数组 a 从前往后依次分组,使得每组之和都刚好小于x,即加上后一个数就变成大于等于 x 的。如果这样分成 m 组后,仍有多余的数,则说明至少存在一组的和大于等于 x,时间复杂度为 O(n)。


下面给出核心代码:

bool check(int x){ int s = 0, cnt = 0; for(int i = 0; i < n; i++) {        //若单个元素就 >= x 那一定存在        if(a[i] >= x)    return true;   if(s + a[i] < x) s += a[i]; else cnt++, s = a[i]; } return cnt >= m;}
int l = 0, r = 1e9;while(l < r){ int mid = l + r + 1 >> 1; if(check(mid)) l = mid; else r = mid - 1;}

整体复杂度为 O(nlogn)。这里偷了下懒,扩大了 l 和 r 的范围。实际上 log1e9 < 30,扩大范围并不会增加几次循环。


本题思维上还是有一点难度的,尤其是 check 函数的实现,请大家好好理解。


2.问题答案具有单调性(二分性)

什么叫答案具有单调性?


可以理解为,如果一个值满足题目中的要求,那么所有大于(或者小于)这个值的数都会满足要求,就可以考虑用二分来做。


与上一题类似,只是没有明显的关键词提示。下面给出模板题:


给定一个长度为 n 的正整数数组 a 以及一个整数 k,其中 n 为奇数。每次操作可以使得其中一个数加 1,最多可进行 k 次操作。求可能得到的最大中位数。

数据范围:1 <= n, ai, k <= 1e5

样例:

输入:
3 2 

2 6 4

输出:
6


类似地,我们可以实现一个 check(x) 函数,功能是”判断数组 a 经过不超过 k 次操作的中位数是否大于等于 x“。


考虑 check 函数的实现,首先要对数组排个序,中位数位置为 (n + 1) / 2(下标从1开始),

添加在中位数之前的数,都可以添加到中位数之后去,因此只要保证 最少要添加的值 小于等于 k 就行了。下面给出核心代码:

bool check(int x) {    long long s = 0//注意数据可能爆int for (int i = (n + 1) / 2; i <= n; ++i) if (a[i] < x) s += x - a[i]; else break; return s <= k;}
int l = 0, r = 1e5;while(l < r){    int mid = l + r + 1 >> 1; if(check(mid)) l = mid; else r = mid - 1;}

当然,求阴影部分的值也可以用二分(最后一个小于 x 的位置)加前缀和优化到 O(logn),但本题是需要排序的,上限已经到了 O(nlogn),没必要再去优化了。


3.小结

通过上面两道题,我们可以发现:二分法的精髓在于把一个”求解问题“转化为”判定问题“,而通常”判定问题“的难度是远远小于”求解问题“的。


只需要一个问题的答案满足有界性和单调性(二分性),即在有限的不同的连续区间上有不同的性质,就可以考虑使用二分(三分)来做。


最后,留给大家一道真实的笔试题练手:

给定 n 个长度为 Li 的绳子,现在需要 m 根等长的绳子,你可以对每根绳子任意裁剪,但不能拼接,求 m 根绳子的最长长度(保留两位小数)。

数据范围:1 <= n, m <= 1e5,  0 < Li < 1e9, Li为整数

样例:
输入:
3 4

3 5 4

输出:

2.50


提示:这是某条的一道笔试题,如果不在二分的标签下,可能很多人的第一映像是 贪心?模拟?DP?

其实,我们可以发现:如果 n 根绳子可以剪成 m 根长度为 k 的绳子,那一定可以剪成更小的。所以,原问题就转化成了求最后一个满足题意数(注意浮点数的比较哦),下期给出标程。


--------------------------

柠檬头LH
分享干货满满的算法和数学知识。
2篇原创内容
Official Account