一、双指针(两类)
双指针算法的核心是将O(n^2)的算法优化到O(n)。

- 类别一:两个指针维护两个序列(如 归并)

- 类别二:两个指针维护一个序列–首尾(如 快排)

1.思想
① 遍历
② 更新
2.模板
for (int i = 0, j = 0; i < n; i ++ ) //i从0开始,j从某一点开始
{
while (j < i && check(i, j)) //每次i更新完,就要更新j
j ++ ; //前提是 j在合法范围内,且满足某种性质
// 具体问题的逻辑
}
常见问题分类:
(1) 对于一个序列,用两个指针维护一段区间
(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
3.题目
(1)将一串英文字符按单词分隔开,单词间以空格分割。
如:
输出: abc dfg
输出:abc
dfg

#include <algorithm>
#include <bits/stdc++.h>
using namespace std;
int main() {
char str[100];
gets(str); //读取一行文本并存储在str中
int n = strlen(str); //计算以 null 字符 '\0' 结束的字符串的长度
for (int i = 0; i < n; i++) {
int j = i;
while (j < n && str[j] != ' ') {
cout << str[j];
j++;
}
cout << endl;
i = j;
}
retur 0;
}
(2)799.最长连续不重复子序列


const int N=10010;
int n;
int a[N],s[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++){ // j表示滑动窗口的左边界
s[a[i]]++; // 存储每个数 出现次数 的数组
// 如果当前数字出现次数大于 1,说明有重复,调整左边界
while(s[a[i]]>1){
s[a[j]]--; //减少左边界指向的数字出现的次数
j++; // 左边界右移
}
res=max(res,i-j+1);
}
cout << res <<endl;
return 0;
}
二、位运算
1.思想&&模板
- 求n的第k位数字: n >> k & 1

int main() {
int n = 10; // 10的二进制表示
for (int k = 3; k >= 0; k--)
cout << (n >> k & 1); // 1010
}
- 返回n的最后一位1:lowbit(n) = n & -n

int lowbit(int x){
return x&-x;
}
2.题目(801.二进制中1d个数)

int lowbit(int x){
return x&-x;
}
int main() {
int n;
cin>>n;
while(n--){
int x;
cin>>x;
int res=0; //存储结果
//只要x不为0,就减去lowbit,减多少次就有多少个1;
while(x){
x=x-lobit(x);
res++;
}
cout<<res<<endl;
}
return 0;
}
三、离散化
若有一个数组范围很大,但元素很少,在保证相对位置不变的前提下,则可以将其下标映射到一个新数组中,减少空间的浪费。

1.思想&&模板
① 数组去重—利用库函数可以实现 排序+去重
vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素
// unique会将重复元素置于数组末尾,并返回独一无二元素中最后一个元素的下标。
//erase 再将unique的返回值作为起点,删除末尾的元素 即完成去重。
- 关于unique方法的实现(利用双指针)

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] 所有a中不重复的数
return a.begin()+j;
}
② 二分求出 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
// +1 表示从1开始映射
}
2.题目(802.区间和)

分析:
(1)n次操作,m次问询,由于每次问询设计两个位置,故该题共涉及n+2 * m个数。
核心:在保证这n+2 * m个数字在相对位置不变的情况下,将他们存入数组,去重排序作为基准,便于进行离散化。
tips:由于1<=n,m<=10000,即最多进行10000次操作和10000次问询,n+2 * m<=30000。故存储这n+2 *m个位置的数组的容量应设置为30000个,同时避免re,令N=30010。
(2)将n次操作中的位置 x 进行离散化,更新数轴。
(3)m次询问中的位置l,r进行离散化,求前缀和。
#include <algorithm>
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII; // 定义双元数组
const int N=300010; //n+2m
int n,m; // n次操作,m次问询
int a[n],s[n]; // a数组模拟数轴,全为0; s数组 存储前缀和
vector<int> alls; // 存储 x,l,r 的数值
vector<PII> add,query; // 数组存储 ①操作参数,②问询参数
int find(x){ // 找大于x的最小值
int l=0,r=a.size()-1;
while(l<r){
int mid = l+(r-l)/2;
if(alls[mid]>=x)
r=mid;
}
return r+1; //
}
int main() {
cin >> n >> m; // 1.1
for(int i=0;i<n;i++){ // 1.2 读入操作参数
int x,c;
cin >> x >> c;
add.push_back({x,c})
alls.push_back(x); // 单独存储 操作 中的 位置参数 x
}
for(int i=0;i<m;i++){ // 1.3 读入问询参数
int l.r;
cin >> l >> r;
query.push_back({l,r});
alls.push_back(l);
alls.push_back(r);
}
//----------------------------------------------
// 2.去重
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
// 3.处理 操作步骤
// 3.1 更新
for(auto item:add){
int x = find(item.first); // 对 x 离散化
a[x] = a[x] + item.second; // 某一位置x上的数+c
}
// 3.2 处理前缀和,为 询问 做准备
for (int i=0;i<alls.size();i++){
s[i]=s[i-1]+a[i];
}
// 4.处理询问
for (auto item:query){
int l=find(item.first);
int r=find(item.second);
cout << s[r]-s[l-1] << endl;
}
return 0;
}
四、区间合并
把有交集的区间合并。
1.思想
(1)按照区间的左端点排序。
(2)区间与区间的关系有三种:包含,相交,不相交。
① 包含则不用管;
② 相交则合并;
③ 不相交则不用合并。
- 综合来说,包含 和 相交 的处理原则可以合并:左端点不变,将右端点更新为 两个区间右端点的最大值。
- ③ 不相交时,则将当前区间加入结果区间,将下一个不相交区间作为当前区间。

2.模板
// 将所有存在交集的区间合并
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;
}
3.题目(803.区间合并)

#include <algorithm>
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
typedef pair<int, int> PII; // 定义双元数组,便于存储区间。
vector<PII> segs;
void merge(vector<PII> &sges) {
vector<PII> res; // 结果数组
sort(segs.begin(), segs.end()); //会先按照第一关键字排序,相同时再按照第二关键字排序。
int st = -2e9, ed = -2e9; // 当前区间的左端点,右端点
for (auto seg : segs) {
if (seg.first > ed) { // 情况3:不相交
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 main() {
int n;
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;
}
五、贪心区间问题
5.1 (AcWing 905.区间选点)
题意:从N个闭区间中选出最少的点覆盖所有区间。
思想:
- 将所有区间的右端点 从小到大 排序
- 枚举每一个区间:
① 若当前区间中已经包含点,则跳过该区间。
② 否则,选点并更新右端点为当前区间的右端点。

#include <iostream>
#include <algorithm>
using namespace std;
const int N=100010;
// 定义区间结构体
struct Range
{
int l,r;
bool operator< (const Range &W)const
{
return r<W.r;
}
}range[N];
int n;
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
int l,r;
cin>>l>>r;
range[i]={l,r};
}
// 1.排序
sort(range,range+n);
// 2.遍历
int res=0,ed=-2e9; //res表示选点的数量,ed为右端点
for(int i=0;i<n;i++)
if(range[i].l > ed) // 该区间与上一区间 未重合
{
res++; // 选点
ed = range[i].r; // 更新右端点
}
cout<<res;
return 0;
}
5.2 (AcWing 908.最大不相交区间数量)
同上一题一样,选出来点的数量==最大不相交区间的数量。

#include <iostream>
#include <algorithm>
using namespace std;
const int N=100010;
struct Range
{
int l,r;
bool operator< (const Range &W)const
{
return r<W.r;
}
}range[N];
int n;
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
int l,r;
cin>>l>>r;
range[i]={l,r};
}
sort(range,range+n);
int res=0,st=-2e9;
for(int i=0;i<n;i++)
if(range[i].l>st)
{
res++;
st=range[i].r;
}
cout<<res;
return 0;
}
5.3 (AcWing 906.区间分组)
1.将所有区间按照 左端点从小到大 排序
2. 遍历所有的区间:使用 小根堆 存储每个组最后一个区间的右端点值。
① 如果当前区间的 左端点 <= 堆中最小右端点 (相交),说明该区间不能放进任何一个分组,则将这个区间加入堆**(新开一组)。
② 如果当前区间左端点 > 堆中最小右端点(不相交),说明该区间能够放进任意一个组中,则将其放入右端点最小的组。将右端点最小的组的右端点更新**

#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
const int N=100010;
struct Range
{
int l,r;
bool operator <(const Range &W)const
{
return l<W.l;
}
}range[N];
int n;
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
int l,r;
cin>>l>>r;
range[i]={l,r};
}
// 1.排序
sort(range,range+n);
priority_queue<int ,vector<int> ,greater<int> > heap; //小顶堆
// 2.遍历
for(int i=0;i<n;i++)
{
if(heap.empty()||heap.top()>=range[i].l) // ① 相交
heap.push(range[i].r);
else
{
heap.pop(); // 起到更新 最小右端点的作用。
heap.push(range[i].r);
}
}
//堆中有多少个最小右端点,就有多少个组。
cout<<heap.size();
}

308

被折叠的 条评论
为什么被折叠?



