二分非常重要的关键就是判断边界是否满足所需的条件!
以下是我常用的两套模板
模板一
//假设需要二分的区间是 [a, b]
int l = a, r = b;
while(l <= r){
mid = l + (r - l) / 2;
if(check()){
r = mid - 1;
//ans = mid; 保留中间值
}else{
l = mid + 1;
}
}
模板二(有两种用法)
//假设需要二分的区间是 [a, b]
//找左边界
int l = a, r = b;
while(l < r){
mid = l + (r - l) / 2;
//把 [a, b] 分为 [a, mid] 和 [mid + 1, r] 两个区间
if(check()){
r = mid; //收缩区间为 [a, mid]
}else{
l = mid + 1; //收缩区间为 [mid + 1, r]
}
}
//找右边界
int l = a, r = b;
while(l < r){
mid = l + (r - l + 1) / 2;
//把 [a, b] 分为 [a, mid +1] 和 [mid, r] 两个区间
if(check()){
l = mid;//收缩区间为 [a, mid]
}else{
r = mid - 1;//收缩区间为 [mid + 1, r]
}
}
H 指数 II 来源:力扣(LeetCode)
给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数,citations 已经按照 升序排列 。计算并返回该研究者的 h 指数。h 指数的定义:h 代表“高引用次数”(high citations),一名科研人员的 h 指数是指他(她)的 (n 篇论文中)总共有 h 篇论文分别被引用了至少 h 次。且其余的 n - h 篇论文每篇被引用次数 不超过 h 次。提示:如果 h 有多种可能的值,h 指数 是其中最大的那个。请你设计并实现对数时间复杂度的算法解决此问题。
输入:citations = [0,1,3,5,6]
输出:3
解释:给定数组表示研究者总共有 5 篇论文,每篇论文相应的被引用了 0, 1, 3, 5, 6 次。
由于研究者有 3 篇论文每篇 至少 被引用了 3 次,其余两篇论文每篇被引用 不多于 3 次,所以她的 h 指数是 3 。
class Solution {
public:
int hIndex(vector<int>& citations) {
int n = citations.size();
int l = 0, r = n - 1;
while(l <= r){
int mid = l + (r - l) / 2;
if(citations[mid] >= n - mid){ // 答案存在
r = mid - 1;
}else{
l = mid + 1;
}
}
// 如果不存在答案
// l = mid + 1
// = r + 1 = n - 1 + 1
// l = n
return n - l;
}
};
模板一的边界符合题目要求,而接下来的模板二就是笔者想讲的边界判断是否满足条件
class Solution {
public:
int hIndex(vector<int>& citations) {
int n = citations.size();
int l = 0, r = n - 1;
while(l < r){
int mid = l + (r - l) / 2;
if(citations[mid] >= n - mid){
r = mid;
}else{
l = mid + 1;
}
}
if(citations[l] < n - l) //这一步很重要,判断边界是否是想要的
l ++; // 答案不存在 l = n
return n - l;
}
};
这只是千万二分类型的题目之一,二分的核心——边界判断掌握才是关键。
找到 K 个最接近的元素 来源:力扣(LeetCode)
给定一个 排序好 的数组 arr ,两个整数 k 和 x ,从数组中找到最靠近 x(两数之差最小)的 k 个数。返回的结果必须要是按升序排好的。
整数 a 比整数 b 更接近 x 需要满足:
|a - x| < |b - x| 或者
|a - x| == |b - x| 且 a < b
解法一:二分查找
class Solution {
public:
vector<int> findClosestElements(vector<int>& arr, int k, int x) {
int l = 0, r = arr.size() - k;
while(l < r){
int mid = l + (r - l) / 2;
if(x - arr[mid] <= arr[mid + k] - x) r = mid;
// arr[mid] 和 arr[(mid+k-1)+1] 比较才会更新
else l = mid + 1;
}
vector<int> p;
for(int i = 0; i < k; i ++) p.push_back(arr[i + l]);
return p;
}
};
有人对于if(x - arr[mid] <= arr[mid + k] - x)
的 arr[mid + k]
有疑问,为什么是 mid
和 mid + k
比较而不是和 mid + k - 1
比较,还有 mid + k
会不会超边界
首先,arr[mid+k-1]
是相对于 mid
的第 k
个数, 必然是符合题目条件的,不需要比较,也不会更新,只有与第 k+1
个数比较才会更新
其次,这个二分是查找左边界,mid
的最大值是 arr.size() - k -1
,加 k
后正好是最后一个元素,所以不会超边界
解法二:滑动窗口
class Solution {
public:
vector<int> findClosestElements(vector<int>& arr, int k, int x) {
int l = 0, r = arr.size() - 1;
while(r - l + 1 > k){
if(abs(x - arr[l]) <= abs(arr[r] - x)) r --;
else l ++;
}
vector<int> p;
for(int i = l; i <= r; i ++) p.push_back(arr[i]);
return p;
}
};