二分搜算是學習演算法的路上會碰到最基本的幾個演算法之一,而且在往後的比賽之中也會時常見到需要使用二分搜的場合,雖然二分搜的概念並不困難,但在實作上會碰到各式各樣的問題,需要隨著需求不同更改程式碼,有著許多小細節要注意,所以我在此編寫了一個通用模板,可以以相似的想法面對不同類型的題目。
二分搜能運用到的場合相當多,只要是單調區間便可使用二分查找,主要的題目類型有:
二分搜說難也難、說簡單也簡單,主要是實作過程中有許多細節需要注意:
就這些細節可知二分搜有許多類型和細節,為求解題方便以下將提出一套思路使你在面對各類型的題目時,都能輕易找出對應的寫法。
給定一單調上升之數列,求第一個大於等於某特定值的元素索引值並定義其為「下界」,當下界不存在則回傳數組長度。
給定數組[1,2,4,5,5,6,7],令特定值為5則應該回傳下界為3
0 | 1 | 2 | ->3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
1 | 2 | 4 | ->5 | 5 | 6 | 7 |
可以看到我們能將數列分為右側都大於等於特定值和左側都小於特定值,而我們回傳之索引值正好是大於等於特定值的數列之下界。
首先,我們以閉區間之寫法來表達思路:
依據上述思路提出,演算法步驟:
可執行下列程式碼以利思考。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int a[100005];
int main(){
int n,x;
cin>>n;
for(int i=0;i<n;++i)cin>>a[i];
cin>>x;
sort(a,a+n);
int left=0,right=n-1;//[0~N-1]的閉區間
while(left<=right){//[left~right]大小為零結束
int mid=(right+left)/2;
//cout<<left<<' '<<mid<<' '<<right<<'\n';
if(a[mid]>=x) right=mid-1;
else left=mid+1;
}
cout<<left<<'\n';//回傳索引值
return 0;
}
給定一單調上升之數列,求最後一個小於等於某特定值的元素索引值並定義其為「上界」。
一樣給定數組 [1,2,4,5,5,6,7],令特定值為則應該回傳上界為4
0 | 1 | 2 | 3 | ->4 | 5 | 6 |
---|---|---|---|---|---|---|
1 | 2 | 4 | 5 | ->5 | 6 | 7 |
首先我們一樣能將數列分為右側都大於特定值和左側都小於等於特定值,透過觀察我們可以發現,小於等於特定值的數列上界和大於特定值下界是相鄰的所以 上界 = 下界 - 1。因此,所有找上界的问题,都可以轉換為「互補的」找下界的問题。
依據之前的思路提出相同的演算法步驟:
可執行下列程式碼以利思考。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int a[100005];
int main(){
int n,x;
cin>>n;
for(int i=0;i<n;++i)cin>>a[i];
cin>>x;
sort(a,a+n);
int left=0,right=n-1;//[0~N-1]的閉區間
while(left<=right){//[left~right]大小為零結束
int mid=(right+left)/2;
//cout<<left<<' '<<mid<<' '<<right<<'\n';
if(a[mid]>x) right=mid-1;
else left=mid+1;
}
cout<<right<<'\n';//回傳索引值
return 0;
}
二分搜無論是找下界、還是找上界,都可以套用「找下界」的思路來實作:
首先是循環條件 若是閉區間的話以left <= right,表示區間不為空,開區間則是以left < right,表示區間不為空。
再來if的判定條件和問題的比較規則是相同的,比如要找满足 x >= target 的第一个元素,就令 if a[mid] >= target;要找滿足 x > target 的第一个元素,就令 if a[m] > target。
接著if為真時,以閉區間為例需更新 right:right = mid - 1;否則 left = mid + 1;而開區間則是更新 right:right = mid ;否則 left = mid + 1。
最後當循環结束時,閉區間的left會指向下界,right則指向「互補條件」的上界;而開區間的left和right則都指向下界
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int a[100005];
int main(){
int n,x;
cin>>n;
for(int i=0;i<n;++i)cin>>a[i];
cin>>x;
int left=0,right=n;//[0~N-1]的開區間
while(left<right){//[left~right)大小為零結束
int mid=(right+left)/2;
//cout<<left<<' '<<mid<<' '<<right<<'\n';
if(a[mid]>=x) right=mid;
else left=mid+1;
}
cout<<left<<'\n';//回傳索引值
return 0;
}
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int a[100005];
int main(){
int n,x;
cin>>n;
for(int i=0;i<n;++i)cin>>a[i];
cin>>x;
cout<<lower_bound(a,a+n,x)-a<<'\n';
//通過返回的地址減去起始地址begin,得到索引值
return 0;
}
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int a[100005];
int main(){
int n,x;
cin>>n;
for(int i=0;i<n;++i)cin>>a[i];
cin>>x;
cout<<upper_bound(a,a+n,x)-a<<'\n';
//通過返回的地址減去起始地址begin,得到索引值
return 0;
}
#include<bits/stdc++.h>
using namespace std;
int a[100005];
int main()
{
int n,x;
cin>>n;
for(int i=0;i<n;++i)cin>>a[i];
cin>>x;
if(binary_search(a,a+n, x))
cout << "found" <<'\n';
else
cout << "not found" <<'\n';
return 0;
}