基础算法(三)——双指针、位运算、离散化、区间合并

前言

重学算法第2天,希望能坚持打卡不间断,从基础课开始直到学完提高课。
预计时长三个月内,明天再来!肝就完了

2月14日,day02 打卡
今日已学完y总的
算法基础课-1.4-第一章 基础算法(三)

共4题,知识点如下
双指针算法: 最长连续不重复子序列
位运算:二进制中1的个数
离散化:区间和
区间合并:区间合并
(离散化和区间合并后期还需补补)

双指针算法

共两种形式
在这里插入图片描述

模板

for (int i = 0, j = 0; i < n; i ++ )
{
    while (j < i && check(i, j)) j ++ ;

    // 具体问题的逻辑
}
常见问题分类:
    (1) 对于一个序列,用两个指针维护一段区间
    (2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作

简单应用
例题1:输出并打印每个单词,各占一行
在这里插入图片描述

// 最简单的双指针算法应用,一维情况

#include <iostream>
#include <string.h>

using namespace std;

int main() {
    char str[1000];
    
    gets(str); // 好像需要使用fgets
    
    int n = strlen(str);
    
    for (int i = 0; i < n; i++) {
        int j = i;
        while (j < n && str[j] != ' ') j++;
        
        //这道题的具体逻辑
        for (int k = i; k < j; k++) cout << str[k];
        cout << endl;
        
        i = j;
    }
    return 0;
}

AcWIng 799. 最长连续不重复子序列

模板题

思路:
在这里插入图片描述

#include <iostream>

using namespace std;

const int N = 100010;

int n;
int a[N]; // 原数组
int s[N]; // 当前j和i区间内每个数出现的次数

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) { // 新加的数重复了才走while
            // 如果j和i区间内还是有重复的数,就把j对应的值剔除
            s[a[j]] --; // 把j拿出去
            j++;
        }
        res = max(res, i - j + 1);
    }
    cout << res << endl;
}
/*如下      i走了2步        j走了2步(因为ij区间内有重复,没重复的时候j不走)
1 2 2 3 5    --->  1 2 2 3 5  -->  1 2 2 3 5  -----> 1 2 2 3 5  
i	                   i			   i                     i
j                  j				   j				 j
*/ 

总结:先写个暴力,然后看 i 和 j 之间有没有单调关系,有的话就用这个关系将算法从O(n2)降到O(n)

位运算

模板

求n的第k位数字: n >> k & 1
返回n的最后一位1lowbit(n) = n & -n

看n的 第k位是几

在这里插入图片描述

#include <iostream>
#include <string.h>

using namespace std;

int main() {
    int n = 10;
    for (int k = 3; k >= 0; k--) 
        cout << (n >> k & 1);
    
    return 0;
}
// 1010

返回 x 的最后一位1:lowbit(x)
-x = ~x + 1
x & -x = x &(~x + 1)

在这里插入图片描述

#include <iostream>
#include <string.h>

using namespace std;

int main() {
    int n = 10;
    unsigned int x = -n;
    for (int k = 31; k >= 0; k--) 
        cout << (x >> k & 1);
    
    return 0;
}
// 11111111111111111111111111110110

计算机用补码存数据

在这里插入图片描述

AcWing 801. 二进制中1的个数

实际应用,统计x(二进制)里面1的个数

思路:每次把最后一个1去掉,当x=0时,减了多少次就有多少1

#include <iostream>

using namespace std;

int lowbit(int x) {
    return x & -x;
}

int main() {
    int n;
    cin >> n;
    while (n--) {
        int x;
        cin >> x;
        
        int res = 0;
        while (x) {
            x -= lowbit(x); // 每次减去x最后一位1
            res++;
        }
        cout << res << " ";
    }
    return 0;
}

离散化

(特指整数)

在这里插入图片描述

模板

vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重复元素
// unique 会将重复元素移到数组最后面,返回值是存放重复元素的起始坐标
// erase 删掉后半部分

// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于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; // 映射到1, 2, ...n
}

在这里插入图片描述

AcWing 802. 区间和

思路:

数组下标较小时,可以用前缀和来做
a[x] += c
S[r] - S[l-1]

在这里插入图片描述

本题最多用 n+2m(l和r) 个坐标 — 3 x 10^5

由于值域很大,但里面的数很稀疏,要用到的只有他们间的相对关系,
每次要求数的和时,其实只用求L-R之间所有数的和即可,
跟数的绝对值无关,只与数的相对大小有关
所以可以把用到过的数映射成从1开始的自然数

映射完之后数在1 —3 x 10^5之间,可以用前缀和来解决

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;
// 每种操作都是2个数,用pair来存
typedef pair<int, int> PII;

const int N = 300010;

int n, m;
int a[N], s[N]; // a[]: 存的数 s:[] 前缀和

vector<int> alls; // 离散化后的结果
vector<PII> add, query;

// 求x离散化后的结果
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; // 映射到从1开始的数,因为要用前缀和,从1开始比较好做
}

int main() {
    cin >> n >> m;
    for (int i = 0; i < n; i++) {
        int x, c;
        cin >> x >> c;
        add.push_back({x, c});// 下标x的位置加上c
        //add.push_back(x, c);// 没加{} 
        
        alls.push_back(x); // 把x加到待离散化的数组里
    }
    
    for (int i = 0; i < m; i++) {
        int l, r;
        cin >> l >> r;
        query.push_back({l ,r}); // 插入pair,需要用{}括起来
        alls.push_back(l);
        alls.push_back(r);
    }
    
    // 去重
    sort(alls.begin(), alls.end());
    alls.erase(unique(alls.begin(), alls.end()), alls.end());
    
    // 处理插入
    for (auto item : add) { 
        int x = find(item.first); // 求x离散化后的结果
        a[x] += item.second; // 在离散化后的位置上加上要加的数
    }
    
    // 预处理前缀和
    for (int i = 1; i <= alls.size(); i++) s[i] = s[i - 1] + a[i];
    
    // 处理询问
    for (auto item : query) {
        // item.first 左区间的值
        int l = find(item.first), r = find(item.second); // 左右端点离散化后的值
        // 中间所有数的个数
        cout << s[r] - s[l- 1] << endl; // 前缀和公式
    }
    
    return 0;
}

离散化好像没完全懂,以后再补补

区间合并

只要有相交的部分就合并,哪怕只是端点相交
在这里插入图片描述

  1. 区间内部------不变
  2. 区间部分重合—更新ed
  3. 区间外----------将区间存入结果

模板

// 将所有存在交集的区间合并
void merge(vector<PII> &segs)
{
    vector<PII> res;

    sort(segs.begin(), segs.end());

    int st = -2e9, ed = -2e9;
    for (auto seg : segs)
        if (ed < seg.first)
        {
            if (st != -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;
}

AcWing 803. 区间合并

思路:
在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

typedef pair<int, int> PII;

const int N = 100010;

int n;
vector<PII> segs;

void merge(vector<PII> &segs) {
    vector<PII> res;
    
    sort(segs.begin(), segs.end());
    
    int st = -2e9, ed = -2e9; // 设置边界值
    for (auto seg : segs) { // 从前往后扫描所有线段
        if (ed < seg.first) { // 维护的区间右端点 未与枚举的区间相交
            if (st != -2e9) res.push_back({st, ed}); // st != -2e9 :防止输入区间为空
            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;
}

希望自己能坚持打卡不间断,学好算法和C++

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值