• 第一章 基础算法(三)


    双指针算法

    双指针算法分类

    image-20220731112247867

    双指针算法模板

    image-20220731112316354

    性质:

    image-20220731112608699

    总结

    image-20220731113656500

    为什么双指针算法可以起到优化的作用:

    i:0->n

    j:0->n 在每次循环完毕,j不需要归零,因此j也是0->n

    两个指针最多为2n

    例1

    输入字符串,把其中每一个单词输出来,单词之间用空格隔开
    
    • 1
    #include 
    #include 
    
    using namespace std;
    
    int main()
    {
        char str[1000];
    
        gets(str);
        int n = strlen(str);
    
        for(int i = 0;str[i];i++)
        {
            int j = i;
            //分清楚
            while(j < n && str[j] != ' ') j++;
            //这道题的具体逻辑
            for(int k = i;k < j;k++) printf("%c",str[k]);
            printf("\n");
            i = j;
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    例2

    双指针怎么想:可以从暴力的方式想,然后利用单调性改进

    给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
    
    输入格式
    第一行包含整数 n。
    
    第二行包含 n 个整数(均在 0∼105 范围内),表示整数序列。
    
    输出格式
    共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。
    
    数据范围
    1≤n≤105
    输入样例:
    5
    1 2 2 3 5
    输出样例:
    3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    image-20220731115647238

    s[N]

    s[a[i]]]++

    //799
    #include 
    
    using namespace std;
    
    const int N = 100010;
    int a[N];
    int s[N];  //计数器,每个数出现的次数
    int n;
    int main()
    {
        cin>>n;
        for(int i = 0;i < n;i++ ) cin >> a[i];
        
        int res = 0;
        for(int i = 0,j=0 ;i < n;i++)
        {
            s[a[i]]++;
            while(s[a[i]] > 1)
            {
                s[a[j]]--;  //j表示弹出数据,对应统计的数量降低
                j++;  //向前移动
            }
            res = max(res,i-j+1);  //因为最后一次可能不是最大的,该值用于统计累加过程中整个串中最大的一次数值记录
        }
        cout<< res <<endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    位运算

    二进制的第k位

    image-20220731143615984

    //n的二进制表示中第k位
    #include 
    #include 
    
    using namespace std;
    int main()
    {
        int n = 10;
        for(int k = 3;k>=0;k--)
            //向右移动k位
            cout<< (n>>k&1);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    lowbit 返回x的最后一位1

    给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数。
    
    输入格式
    第一行包含整数 n。
    
    第二行包含 n 个整数,表示整个数列。
    
    输出格式
    共一行,包含 n 个整数,其中的第 i 个数表示数列中的第 i 个数的二进制表示中 1 的个数。
    
    数据范围
    1≤n≤100000,
    0≤数列中元素的值≤109
    输入样例:
    5
    1 2 3 4 5
    输出样例:
    1 1 2 1 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    image-20220731144251751

    实现

    lowbit的实现:x & -x

    ~代表数值取反

    image-20220731144352526

    image-20220731144552552

    应用:统计x中1的个数。

    要先学会走路,再学会飞。不能还不会走路,就去飞。一步步来,不然会摔得很惨

    #include 
    
    using namespace std;
    
    int n;
    
    int lowbit(int x)
    {
        return x & -x;
    }
    int main()
    {
        cin>>n;
        //针对每一个数值进行处理,不需要存储相关信息
        while(n--)
        {
            int x;
            cin>>x;
            int res = 0;
            while(x) x -= lowbit(x),res++;  //每次减去x的最后一位
            cout<<res<<' ';
        }
        return 0;
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    计算机中编码知识

    image-20220731145447925

    解释为什么反码是取反后加一,而不是直接取反?

    计算机底层实现没有减法,减法由加法实现

    image-20220731145703454

    做题思路

    人类做题的过程就是dfs的过程

    image-20220731150124215

    有同学学习比较快,或者接触比较早,或者对各种算法已经熟悉了


    离散化

    假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。
    
    现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。
    
    接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r] 之间的所有数的和。
    
    输入格式
    第一行包含两个整数 n 和 m。
    
    接下来 n 行,每行包含两个整数 x 和 c。
    
    再接下来 m 行,每行包含两个整数 l 和 r。
    
    输出格式
    共 m 行,每行输出一个询问中所求的区间内数字和。
    
    数据范围
    −109≤x≤109,
    1≤n,m≤105,
    −109≤l≤r≤109,
    −10000≤c≤10000
    输入样例:
    3 3
    1 2
    3 6
    7 5
    1 3
    4 6
    7 8
    输出样例:
    8
    0
    5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    特指整数离散化,保序离散化

    image-20220731150745064

    image-20220731151018801

    image-20220731152706736

    image-20220731152921632

    image-20220731160546759

    #include 
    #include 
    #include 
    
    using namespace std;
    
    typedef pair<int,int> PII;
    
    const int N = 300010;
    int n,m;
    int a[N],s[N];
    
    vector<int> alls;
    
    vector<PII> add,query;
    
    int find(int x)
    {
        int l = 0,r = alls.size()-1;
        while(l < r)
        {
            int mid = l + r >>1;
            if(alls[mid] >= x) r = mid;
            else l = mid + 1;
        }
        return r+1;
    }
    
    //java/python中无unique函数,如何用代码实现
    /*
    vector::iterator unique(vector &a)
    {
        int j = 0;
        for(int i = 0 ; i < a.size();i++)
            if(!i || a[i] != a[i-1])
                a[j++] = a[i];
        //a[0]~a[j-1] 所有a中不重复的数
        return a.begin() + j;
    }
    */
    
    int main()
    {
        cin>>n>>m;
        for(int i = 0;i<n;i++)
        {
            int x,c;
            cin>>x>>c;
            add.push_back({x,c});
            alls.push_back(x);
        }
        for(int i = 0;i<m;i++)
        {
            int l,r;
            cin>>l>>r;
            query.push_back({l,r});
            alls.push_back(l);
            alls.push_back(r);
        }
        //去重
        sort(alls.begin(),alls.end());
        // 使用C++库中unique函数
        alls.erase(unique(alls.begin(),alls.end()),alls.end());
        //手写后unique函数
        //alls.erase(unique(alls),alls.end());
        
        //处理插入
        for(auto item : add)
        {
            int x = find(item.first);
            a[x] += item.second;
            
        }
        
        //预处理前缀和
        for(int i = 1;i <= alls.size();i++) s[i] = s[i-1] + a[i];
        
        //处理询问
        for(auto item : query)
        {
            int l = find(item.first), r =find(item.second);
            cout<< s[r] -s[l-1]<<endl;
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    区间合并

    给定 n 个区间 [li,ri],要求合并所有有交集的区间。
    
    注意如果在端点处相交,也算有交集。
    
    输出合并完成后的区间个数。
    
    例如:[1,3][2,6] 可以合并为一个区间 [1,6]。
    
    输入格式
    第一行包含整数 n。
    
    接下来 n 行,每行包含两个整数 l 和 r。
    
    输出格式
    共一行,包含一个整数,表示合并区间完成后的区间个数。
    
    数据范围
    1≤n≤100000,109≤li≤ri≤109
    输入样例:
    5
    1 2
    2 4
    5 6
    7 8
    7 9
    输出样例:
    3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    image-20220731161749941

    image-20220731161852144

    image-20220731162404742

    image-20220731162615600

    区间合并的思路如下:

    1. 确定一个标准区间
    2. 从候选区间中拿出一个比较,可以分为两种情况
      1. 起始区间在标准区间中
      2. 起始区间在标准区间外
    3. 将最后的区间加入至结果中

    image-20220806085821768

    //Acw版本
    #include 
    #include 
    #include 
    
    using namespace std;
    const int N = 100010;
    
    typedef pair<int,int > PII;
    
    int n;
    vector<PII> segs;
    
    void merge(vector<PII> &segs)
    {
        vector<PII> res;  //用于存储合并结果
        
        sort(segs.begin(),segs.end());  //字典序排序,从first元素开始排序
        
        int st =  -2e9,ed = -2e9;  //一开始虚构的标准区间,只初始化一次,不能添加至结果中
        
        for(auto seg:segs)
        {
             if(seg.first > ed)
            //if(ed < seg.first)
            {
                if(ed != -2e9) res.push_back({st,ed});  //将区间放置于结果区间中,不能将一开始虚构的区间至于结果中
                st = seg.first,ed = seg.second;  //区间进行赋值
            }
            else ed = max(ed,seg.second);
            
        }
        if(st != -2e9) res.push_back({st,ed}); //同上
        
        segs = res;
    }
    
    
    int main()
    {
        cin>>n;
        for(int i = 0;i < n;i++)
        {
            int l,r;
            cin>>l>>r;
            segs.push_back({l,r});
        }
        merge(segs);
        cout<<segs.size()<<endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    自我理解版:

    #include 
    #include 
    #include 
    
    using namespace std;
    
    typedef pair<int,int > PII;
    vector<PII> segs;
    int n,l,r;
    
    void merge(vector<PII> &segs)
    {
        vector<PII> res;
        int st = -2e9;
        int ed  = st;
        sort(segs.begin(),segs.end());
        for(auto seg:segs)
        {
            if(seg.first > ed)
            {
                if(ed != -2e9 ) res.push_back({st,ed});  //不能将一开始虚构的区间至于结果中
                st = seg.first, ed = seg.second;
            }
            else
                ed = max(seg.second,ed);
        }
        if(ed != -2e9 ) res.push_back({st,ed});  //同上
        segs = res;
    }
    int main()
    {
        scanf("%d",&n);
        while(n--)
        {
            scanf("%d%d",&l,&r);
            segs.push_back({l,r});
        }
        merge(segs);
        printf("%ld",segs.size());
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
  • 相关阅读:
    安卓离线点击第一条通知跳转正常,第二条失败或者通过URL Scheme唤起App指定页面第一次成功,第二次失败
    Express的基本使用app.post()app.get()res.send()
    (LeetCode C++)有效的括号
    springboot 使用多线程和线程池
    LeetCode 146:LRU 缓存
    2022-11-30 Github Forking 工作流模式
    SparkMlib 之决策树及其案例
    合并K个升序链表
    【JavaScript】video标签配置及相关事件:
    android kotlin学习
  • 原文地址:https://blog.csdn.net/m0_49448331/article/details/126189463