算法|基础算法|二分和双指针

基础算法|二分

1.基础二分
————细解mid=l+r+1>>1————
2.双指针

心有猛虎,细嗅蔷薇。你好朋友,这里是锅巴的C\C++学习笔记,常言道,不积跬步无以至千里,希望有朝一日我们积累的滴水可以击穿顽石。
在这里插入图片描述

基础二分

模板

bool check(int x){/*...*/} //检查是否满足某种性质

//区间[l,r]被划分成[l,mid]和[mid+1,r]时使用:
int bsearch_1(int l,int r){//尽量往左找目标
    while(l<r){
        int mid = l+r>>1;
        if(check(mid)) r=mid;//check()判断mid是否满足性质
        else l=mid+1;
    }
    return l;
}
//区间[l,r]被划分为[l,mid-1]和[mid,r]时使用:
int bsearch_2(int l,int r){//尽量往右找目标
    while(l<r){
        int mid=l+r+1>>1;
        if(check(mid)) l=mid;
        else r=mid-1;
    }
    return l;
}

题目描述
给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
如果数组中不存在该元素,则返回 -1 -1。
输入格式
第一行包含整数 n 和 q,表示数组长度和询问个数。
第二行包含 n 个整数(均在 1∼10000 范围内),表示完整数组。
接下来 q 行,每行包含一个整数 k,表示一个询问元素。
输出格式
共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回 -1 -1。
数据范围
1≤n≤100000
1≤q≤10000
1≤k≤10000
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1

实践代码:

void solve(){
    int n,q;cin>>n>>q;
    vector<int> a(n);
    for(int i=0;i<n;i++) cin>>a[i];
    while(q--){
        int k;cin>>k;
        int l=0,r=n-1;
        while(l<r){//从右向左找到起始位置
            int mid=l+r>>1;
            if(a[mid]>=k) r=mid;//check
            else l =mid+1;
        }
        if(a[l]!=k) {cout<<"-1 -1"<<endl;continue;}//没找到
        if(a[l]==k) cout << l << ' ';
        while (l < r) {//从左向右找到终止位置
            int mid = l+r+1>>1;//(下面会详细解释)
            if (a[mid]<=k) l=mid;//check
            else r =mid-1;
        }
        cout << l << endl;
    }
}

细解mid=l+r+1>>1

常识1>>1:即二进制数右移1位,也即mid=(l+r)/2
常识2: 除数向下取整:计算机中整数除法会如果是小数会向下取整,如9/2=4.5 -> 4
以上两个常识我们可以清楚,二分中mid本身是会向左缩小范围的(mid = l+r>>1/mid = l+r+1>>1)
模板一中,我们的目的就是尽量往左(取值小)找目标,即向左缩短范围,此时mid=l+r就不需要再+1
模板二中,我们的目的是尽量往右找目标,即向右缩短范围,
假设mid=l+r>>1,此时更新方式是l=mid,如果此时最后状态为l=r-1,那么mid=r+r-1/2 向下取整-> mid=r-1=l,而再次更新l=mid,此时已经明显看出二分陷入死循环,所以我们要mid=l+r+1,避免这种右缩时出现死循环的情况发生

双指针

双指针:两个变量模拟指针的功能在数组间移动
1.两个指针之间的区间1具备某种特征
2.两个指针所指的东西要满足一些条件

题目描述
给定一个长度为n的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
输入格式
第一行包含三个整数n。
第二行包含n个整数(均在0~105范围内),表示整数序列。
输出格式
共1行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。
数据范围
1 ≤ n ≤ 105
输入样例
5
1 2 2 3 5
输出样例
3

实践代码:

void solve(){
    int n;cin>>n;
    int ans=0;
    vector<int> a(n+1);
    map<int,int> mp;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=0,j=0;i<n;i++){
        mp[a[i]]++;
        while(mp[a[i]]>1) {mp[a[j]]--;j++;}
        ans=max(ans,i-j);
    }
    cout<<ans;
}

例题 (双指针+二分)
题目描述
给定一个长度为n的数组a和一个整数m,求其中有多少对二元组(ai,aj)满足:
1 ≤ i ≤ j ≤ n 且 ai+aj > m
输入格式
第一行两个整数n,m,意义如上。(1≤n≤2x105,1≤m≤109)
第二行n个整数,第i个整数表示ai。(0≤ai≤109)
输出格式
一个数字,表示满足条件的二元组的个数。
输入样例
53 5
2 3 4
输出样例
32

注意
设i,j为双指针,定右端点j,j每次向右+1,i在小于j的区间二分找a[i]+a[j]>m的第一个下标i

补充:
lower_bound()/upper_bound()

/*lower_bound()/upper_bound()
     *在已升序排序的元素中,应用二分查找检索指定元素,返回对应元素迭代器位置,找不到则返回尾迭代器
     * lower_bound():寻找>=x的第一个元素的位置
     * upper_bound():寻找>x的第一个元素的位置
     * 怎么找<=x/<x的第一个元素呢?
     * >x的第一个元素的前一个元素(如果有)便是<=x的第一个元素
     * >=x的第一个元素的前一个元素(如果有)便是<x的第一个元素
     * 返回的是迭代器,如何转成下标索引呢?减去头迭代器即可。
     * */
    cout<<"lower_bound()/upper_bound():"<<endl;
    vector<int> arr4{0,1,1,1,8,9,9};
    //找下标(位置)
    int pos1 = lower_bound(arr4.begin(),arr4.end(),8)-arr4.begin();
    cout<<pos1<<endl;
    int pos2 = upper_bound(arr4.begin(),arr4.end(),8)-arr4.begin();
    cout<<pos2<<endl;
    //找不到返回尾迭代器
    vector<int>::iterator it = lower_bound(arr4.begin(),arr4.end(),99);
    int idx = it - arr4.begin();
    cout<<idx<<endl;

    int pos3= upper_bound(arr4.begin(),arr4.end(),99)-arr4.begin();
    if(pos3==arr4.size()){
        cout<<"No Found!"<<endl;
    }

在这里插入图片描述

实践代码:

void solve(){
   int n,m;cin>>n>>m;
   for(int i=1;i<=n;i++)cin>>a[i];
   sort(a+1,a+1+n);
   int ans=0;
   for(int j=1;j<=n;j++){
       int i= upper_bound(a+1,a+j,m-a[j])-a;//底层是二分,找到小于j且a[i]>m-a[j]的第一个下标i
       ans+=j-i;
   }
   cout<<ans<<endl;
}

心有猛虎,细嗅蔷薇。再见了朋友~

  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值