算法学习笔记11.02

第一章 基础算法(三)

双指针算法

可以是两个指针分别指向两个序列,也可以是两个指针指向一个序列,维护一段区间

核心思想:将 O(n2) 优化到 O(n)

最长连续不重复子序列

最长的不包含重复数字的连续子序列的长度

1 2 2 3 5 -> 2 3 5 len = 3

暴力做法:先枚举终点,再枚举起点(j为起点,i为终点)

for(int i = 0; i < n; i ++)
{
    for(int j = 0; j <= i; j ++)
    {
        if(check(j, i))
        {
            res = max(res, i - j + 1);
        }
    }
}

优化做法:

本质上还是枚举每一个i,看左边的j离他最远的话是在哪个位置

我们现在只需要枚举每一个i,然后判断j需不需要向后移动

//双指针算法
for(int i = 0; i < n; i ++)
{
    while(j <= i && check(j, i))	j ++;
    
    res = max(res, i - j + 1);
}

所以双指针算法本质就是对于朴素算法,找到一些单调性来简化复杂度

那么j肯定是不可能往左移动的

因为数据范围较小,所以开一个数组来表示区间内元素的个数

默认之前的j到i的区间内就是此情况下的最长连续不重复子序列

i右移,S[a[i]] ++; 假如此时有S[] > 1,那么这个数一定是a[i] (因为加的是a[i]),

那么check函数写成a[i] != a[i] 就可以了,假如a[j] == a[i]了,j++就跳出了循环 ,如果不相等,就要继续j++,S[a[j]] –

这个题目就是枚举每一个右端点,左端点只可能右移,并且当前一个区间是无重复的,假如新加入的a[i]和前面重复了,就要右移j,直到a[i]只出现过一次,这样的思想。

双指针算法算法模板:

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

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

位运算

求n的二进制表示中第k位为几

n = 15 = (1111)2

  1. 先把第k位移到最后一位 n >> k
  2. 看个位是几 x & 1

lowbit:返回x的最后一位1

x= (1010)2 lowbit(x) = (10)2

x = (101000)2 lowbit(x) = (1000)2

lowbit(n) = n & -n = n & (~ x + 1)

可以用于求n里面1的个数

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

int x;
cin>>x;
int res = 0;
while(x)	x -= lowbit(x), res ++;		//每次减去x的最后一位1
cout<<res;

位运算算法模板:

求n的二进制表示中第k位数字: n >> k & 1
返回n的最后一位1lowbit(n) = n & -n

离散化

整数的离散化

一个数列,值很大,但是数量不多(数据范围在[0, 109] ,数量在[0, 105] )

a[] : 1 , 3 , 100, 2000, 500000 这几个下标的元素有用,其他都没有用

b[] : 0 , 1 , 2 , 3 , 4

问题:

  1. a中可能有重复元素,需要去重
  2. 如何算出a[i]离散化后的值是多少(二分)

假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。

首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。

接下来,进行 m次询问,每个询问包含两个整数 l 和 r,你需要求出在区间[l,r] 之间的所有数的和。

值域跨度很大,但是非常稀疏

将所有会用到的下标离散化映射到一个稠密数组内,在稠密数组内进行前缀和,考虑的是相对关系

注意点:

  1. 一定要注意映射到0开始还是1开始的序列!!!

离散化算法模板:

vector<int> alls; // 存储所有待离散化的值


sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重复元素

// 二分求出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
}

unique函数

对于一个有序数列,如何判断不是重复的

  1. 是第一个元素
  2. 和前一个元素不一样, a[i] != a[i - 1]
vector<int>::iterator unique(vector<int> &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]中所有不重复的数
    return a.begin() + j;
}

区间合并

给很多区间,假如两个区间有交集,就合并为一个区间

  1. 按照所有区间的左端点排序

  2. 从左到右扫描所有的区间,再扫描的过程中进行处理

  3. 假如下一个起始点和终止点在当前区间内,就不变

    假如下一个起始点在当前区间内,但是终止点在当前区间外,就更新当前区间的右端点

    假如下一个起始点和终止点都在当前区间外,就将当前区间存入结果,继续扫描下一区间

  4. 或者进一步简化一下

    假如下一起始点在当前区间内,就让当前区间的右端点变成 当前右端点和下一终止点的较大值

    假如下一起始点在当前区间外,就将当前区间存入答案并继续扫描下一区间

区间合并算法模板:

// 将所有存在交集的区间合并
void merge(vector<PII> &segs)
{
    vector<PII> res;
    
	//首先对原数组进行排序
    //pair是先对第一关键词排序,再对第二关键词排序
    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算法基础课,算法,学习,c++)