在算法学习中,二分答案算法是一种非常高效且常用的技巧。它的核心思想是通过不断缩小搜索范围,逐步逼近目标答案。相比传统的暴力搜索,二分答案算法的时间复杂度通常为 O(logn),特别适合处理大规模数据的查找问题。
本文将详细介绍二分答案算法的两种常见模板,并结合实际应用场景,帮助你更好地理解和使用这一算法。
二分答案算法的基本原理
二分答案算法的核心思想是:在一个有序的区间中,通过不断将区间分成两部分,判断答案可能存在的那一部分,从而逐步缩小搜索范围,最终找到目标答案。
这种算法特别适合以下几种场景:
-
寻找符合要求的区间的最大值或最小值:例如,找到一个数组中第一个满足条件的元素。
-
寻找最大值的最小值:例如,在分配问题中找到最小的最大分配值。
-
寻找最小值的最大值:例如,在优化问题中找到最大的最小值。
这些场景通常难以通过传统的暴力搜索高效解决,而二分答案算法可以轻松应对。
二分答案算法的适用场景
在实际编程中,二分答案算法的应用非常广泛。以下是一些常见的适用场景:
-
寻找符合要求的区间的边界:
-
例如,找到一个有序数组中第一个大于等于某个值的元素。
-
或者找到最后一个小于等于某个值的元素。
-
-
优化问题中的极值查找:
-
例如,在分配问题中,找到最小的最大分配值。
-
或者在调度问题中,找到最大的最小时间间隔。
-
-
寻找满足条件的最优解:
- 例如,在矩阵中找到满足条件的最小元素。
二分答案算法的两种模板
模板一:查找左边界和右边界
查找左边界 (Search Left, SL)
int SL(int l, int r) {
while (l < r) {
int mid = l + r >> 1; // 等价于 (l + r) / 2
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
查找右边界 (Search Right, SR)
int SR(int l, int r) {
while (l < r) {
int mid = l + r + 1 >> 1; // 需要 +1 防止死循环
if (check(mid)) l = mid;
else r = mid - 1;
}
return r;
}
模板一的特点
-
查找左边界:
-
mid
的计算方式为(l + r) / 2
。 -
如果
check(mid)
为真,则说明目标可能在左半部分,将右边界r
更新为mid
。 -
否则,将左边界
l
更新为mid + 1
。
-
-
查找右边界:
-
mid
的计算方式为(l + r + 1) / 2
,这是为了避免死循环。 -
如果
check(mid)
为真,则说明目标可能在右半部分,将左边界l
更新为mid
。 -
否则,将右边界
r
更新为mid - 1
。
-
模板二:另一种实现方式
查找左边界 (Search Left, SL)
int SL(int l, int r) {
while(l <= r) {
int mid = (l + r) >> 1;
if(check(mid)) {
r = mid - 1; // 继续在左半部分查找
} else {
l = mid + 1; // 继续在右半部分查找
}
}
return l;
}
查找右边界 (Search Right, SR)
int SR(int l, int r) {
while(l <= r) {
int mid = (l + r) >> 1;
if(check(mid)) {
l = mid + 1; // 继续在右半部分查找
} else {
r = mid - 1; // 继续在左半部分查找
}
}
return r;
}
模板二的特点
-
查找左边界:
-
如果
check(mid)
为真,则说明目标可能在左半部分,将右边界r
更新为mid - 1
。 -
否则,将左边界
l
更新为mid + 1
。 -
最终返回
l
,即左边界。
-
-
查找右边界:
-
如果
check(mid)
为真,则说明目标可能在右半部分,将左边界l
更新为mid + 1
。 -
否则,将右边界
r
更新为mid - 1
。 -
最终返回
r
,即右边界。
-
模板对比与适用场景
模板一的优点
-
代码更简洁:逻辑清晰,适合快速实现。
-
适用于精确查找边界:特别适合需要找到第一个满足条件的元素(左边界)或最后一个满足条件的元素(右边界)。
模板二的优点
-
更统一:求mid的时候,不用分+1的情况
-
更直观:容易理解,适合初学者。
-
适用于范围查找:适合需要找到满足条件的元素范围的边界。
选择模板的建议
-
如果你需要快速实现并确保代码简洁,可以选择模板一。
-
如果你需要更直观的逻辑处理和更统一的模板,可以选择模板二。
模版的记忆方法
求左边界则返回L,求右边界则返回R
二分答案算法的实际应用
在实际编程中,二分答案算法的应用非常广泛。以下是一些常见的应用场景和示例:
示例 1:寻找符合要求的区间的最小值
假设你需要在一个有序数组中找到第一个大于等于某个目标值的元素。可以使用模板一的查找左边界方法。
bool check(int mid, int target, vector<int>& arr) {
return arr[mid] >= target;
}
int findFirstGreaterEqual(int target, vector<int>& arr) {
int l = 0, r = arr.size() - 1;
return SL(l, r);
}
示例 2:寻找最大值的最小值
例如,在分配问题中,你需要将一组物品分配给若干人,使得每个人分配到的物品总和的最大值最小。可以使用二分答案算法来寻找这个最小的最大值。
bool check(int mid, vector<int>& items, int k) {
int cnt = 1, sum = 0;
for (int item : items) {
sum += item;
if (sum > mid) {
cnt++;
sum = item;
}
}
return cnt <= k;
}
int findMinMaxAllocation(vector<int>& items, int k) {
int l = *max_element(items.begin(), items.end());
int r = accumulate(items.begin(), items.end(), 0);
return SL(l, r);
}
示例 3:寻找最小值的最大值
例如,在调度问题中,你需要找到最大的最小时间间隔。可以使用二分答案算法来寻找这个最大的最小值。
bool check(int mid, vector<int>& times) {
int cnt = 1, prev = times[0];
for (int time : times) {
if (time - prev >= mid) {
cnt++;
prev = time;
}
}
return cnt >= required;
}
int findMaxMinInterval(vector<int>& times, int required) {
sort(times.begin(), times.end());
int l = 0, r = times.back() - times[0];
return SR(l, r);
}
总结
二分答案算法是一种非常强大的工具,特别适合解决以下几类问题:
-
寻找符合要求的区间的最大值或最小值。
-
寻找最大值的最小值。
-
寻找最小值的最大值。
通过本文介绍的两种模板,你可以根据具体问题选择合适的实现方式。模板一适合快速实现精确查找,而模板二则更适合处理复杂的边界条件。
希望本文能帮助你更好地理解和使用二分答案算法!如果你有任何问题或建议,欢迎在评论区留言。让我们一起探索算法的无限可能!