第一章 基础算法(三)
双指针算法
可以是两个指针分别指向两个序列,也可以是两个指针指向一个序列,维护一段区间
核心思想:将 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
- 先把第k位移到最后一位 n >> k
- 看个位是几 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的最后一位1:lowbit(n) = n & -n
离散化
整数的离散化
一个数列,值很大,但是数量不多(数据范围在[0, 109] ,数量在[0, 105] )
a[] : 1 , 3 , 100, 2000, 500000 这几个下标的元素有用,其他都没有用
b[] : 0 , 1 , 2 , 3 , 4
问题:
- a中可能有重复元素,需要去重
- 如何算出a[i]离散化后的值是多少(二分)
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。
首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。
接下来,进行 m次询问,每个询问包含两个整数 l 和 r,你需要求出在区间[l,r] 之间的所有数的和。
值域跨度很大,但是非常稀疏
将所有会用到的下标离散化映射到一个稠密数组内,在稠密数组内进行前缀和,考虑的是相对关系
注意点:
- 一定要注意映射到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函数
对于一个有序数列,如何判断不是重复的
- 是第一个元素
- 和前一个元素不一样, 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;
}
区间合并
给很多区间,假如两个区间有交集,就合并为一个区间
-
按照所有区间的左端点排序
-
从左到右扫描所有的区间,再扫描的过程中进行处理
-
假如下一个起始点和终止点在当前区间内,就不变
假如下一个起始点在当前区间内,但是终止点在当前区间外,就更新当前区间的右端点
假如下一个起始点和终止点都在当前区间外,就将当前区间存入结果,继续扫描下一区间
-
或者进一步简化一下
假如下一起始点在当前区间内,就让当前区间的右端点变成 当前右端点和下一终止点的较大值
假如下一起始点在当前区间外,就将当前区间存入答案并继续扫描下一区间
区间合并算法模板:
// 将所有存在交集的区间合并
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;
}
标签:int,笔记,11.02,++,算法,alls,端点,区间
From: https://www.cnblogs.com/xushengxiang/p/16851353.html