目录
双指针
双指针双指针顾名思义就是两个指针,两个指针的优势在于对于时间复杂度的优化。时间复杂度这个东西对于算法题来说是非常关键的,有些时候我们发现在 解决算法题的时候,方法完全正确,但是由于算法过于复杂或者步骤过于繁琐的原因,往往不能够在规定的时间内解决问题,判断机会给予我们题目未解决———时间超限(TLE)的警告
双指针算法的优化步骤可以大致分为两步:
第一步,先写出比较暴力的双指针算法(一般为两层for循环)
第二步,结合题目要求,对于暴力算法进行优化
也就是将时间复杂度由O(n^2)优化至O(1)
对于较少数据的处理其优势并不明显,但是对于算法题中一言不合就上万的数据点则有着巨大的优势
分类
双指针问题可以分为两大类
一类为单操作类型,也就是对于一个数组进行操作
另一类为多操作类型,也就是对于多个数组进行操作
单操作类型
假设蓝色为i,绿色为j,黑色铅笔的方向为指针的移动方向
1.双指针从同一位置出发移动方向相同
此类情况多见于字符串当中挑取单词或者某些独特的元素
2.双指针分别从头、尾出发朝着相反方向移动
此类情况的经典用例在于二分查找法
多操作类型
假设蓝色为i,绿色为j,黑色铅笔的方向为指针的移动方向
1.双指针从同一位置出发移动方向相同
经典用例同种排序方式的双数组归并排序
2.双指针分别从头、尾出发朝着相反方向移动
经典用例异种排序方式的双数组归并排序
EG挑单词(单词之间多空格)
#include <iostream>
using namespace std;
int main()
{
string s;
getline(cin,s);
if(s[0] != ' ')s = ' ' + s;
if(s[s.size() - 1] != ' ')s + s + ' ';
int head = 0;
while(s[head] == ' ')head ++ ;
for(int i = head;i < s.size();i ++ )
{
int j = i;
while(s[j] != ' ' && j < s.size())j ++ ;
for(int k = i;k < j;k ++ )cout << s[k];
cout << endl;
while(s[j] == ' ' && j < s.size())j ++ ;
i = j - 1;
}
return 0;
}
EG最长不重复子区间
题目描述
给定一个长度为n的整数序列,请找出最长的不包含重复数字的连续子序列,输出它的长度
输入
第一行包含整数n。
第二行包含n个整数(均在0~100000范围内), 表示整数序列。
输出
共一行,包含一个整数,表示最长的不包含重复数字的连续子序列的长度。
数据范围
1≤n≤100000
输入样例
5
1 2 2 3 5
输出样例
3
源代码
利用双指针、双数组进行打标记
样例解释
绿为i,蓝为j
此时得出ans = 4 - 2 + 1 = 3
#include <iostream>
using namespace std;
const int N = 1000000+10;
int a[N],s[N];
int main()
{
int n,ans = 0;
cin >> n;
for(int i = 0,j = 0;i < n;i ++ )
{
cin >> a[i];
s[a[i]] ++ ;
while(s[a[i]] > 1)
{
s[a[j]] -- ;
j ++ ;
}
ans = max(ans,i - j + 1);
}
cout << ans;
return 0;
}
离散化
使用情况
在某些情况下使用前缀和与差分算法的时候会发现,即使在一维前缀和与差分之中,数组有的时候也开不了题目所给的数据范围那样,那么此时我们就需要利用离散化进行元素下标的重排列与定义了。离散化就是对于元素下标分散的极其不均匀且值域范围极大的数组元素进行优化,在优化过后的所有元素下标都从0(或者从1)开始依次排列,从而能得到一个较小的数组。
打个比方,我们一维数组所能开辟的最大内存相当于我们能够对于中国某个省份的犯罪分子进行范围打击,但是题目给的范围远超我们的理想状态,也就是要求我们对于整个中国范围的犯罪分子进行打击,实际这些犯罪分子并不多,分散的区域较广且稀疏。我们使用离散化呢就是将这些全中国的犯罪分子困在一个市里进行精准的打击
EG区间和
题目描述
假定有一个无限长的数轴,数轴上每个坐标上的数都是0。
现在,我们首先进行n次操作,每次操作将某一位置x 上的数加 c 。
接下来,进行m次询问,每个询问包含两个整数和r,你需要求出在区间I, r之间的所有数的和。
输入
第一行包含两个整数n和m。
接下来n行,每行包含两个整数x和c。
再接下来m行,每行包含两个整数和r。
输出
共m行,每行输出一个询问中所求的区间内数字和。
数据范围
-10^9≤x≤10^9,
1 < n,m≤10^5,
- 10^9≤l≤r≤10^9,
10000 <c < 10000
样例输入
3 3
1 2
3 6
7 5
1 3
4 6
7 8
样例输出
8
0
5
源代码
利用离散化对于元素进行辅助处理
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 1000000+10;
typedef pair<int,int> PII;
vector<PII> A;
vector<PII> segs;
vector<int> idx;
int a[N],s[N];
int find(int x)
{
int l = 0,r = idx.size() - 1;
while(l < r)
{
int mid = l + r >> 1;
if(idx[mid] >= x)r = mid;
else l = mid + 1;
}
return r + 1;
}
int main()
{
int n,m;
cin >> n >> m;
for(int i = 1;i <= n;i ++ )
{
int x,c;
cin >> x >> c;
idx.push_back({x});
A.push_back({x,c});
}
for(int i = 1;i <= m;i ++ )
{
int l,r;
cin >> l >>r;
idx.push_back(l);
idx.push_back(r);
segs.push_back({l,r});
}
sort(idx.begin(),idx.end());
idx.erase(unique(idx.begin(),idx.end()),idx.end());
for(auto item : A)
{
int x = find(item.first);
a[x] += item.second;
}
for(int i = 1;i <= idx.size();i ++ )s[i] = s[i - 1] + a[i];
for(auto item : segs)
{
int l = find(item.first);
int r = find(item.second);
cout << s[r] - s[l - 1] << endl;
}
return 0;
}
区间
区间合并即为数学思想在于计算机实现的一个经典样例
数学区间
区间的合并关键在于对于每个区间我们可以先用PII-vector数组来存,然后依据l也就是左边界进行排序,然后对于排序之后的每个区间进行情况的讨论
抛开各种分散的情况来说,由于下一个区间的左端点最小也是大于等于本区间的左端点,因此对于区间是否合并我们可以优化为以下两种情况:
合并情况
本区间的右端大于等于下一个区间的左端
不合并情况
EG区间和并
题目描述
给定n个区间[I,r],要求合并所有有交集的区间。
注意如果在端点处相交,也算有交集。
输出合并完成后的区间个数。
[1 ,3]和[2,6]可以合并为一个区间[1,6]
输入
第一行包含整数n
接下来n行,每行包含两个整数I和r
输出
共一行,包含一个整数,表示合并区间完成后的区间个数
数据范围
1≤n≤100000
-10^9≤l≤r≤10^9
输入样例
5
1 2
2 4
5 6
7 8
7 9
输出样例
3
源代码
如果为区间合并,区间右端点取本区间和下一个区间的右端点的较大值
如果为区间不合并,且本区间左端点不为默认值,则说明有解,推入临时数组
如果区间一直处于合并的状态,那么左端点一定不为默认值,且最后要将此区间推入临时数组;或者说merge函数处理区间的话,最后一组解是不会在循环之中被推入临时数组,即最后一组解必须手动判断推入
若此组数据无解,则临时数组大小为0,merge函数运行结束,区间数组也为0
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 100000+10;
typedef pair<int,int> PII;
vector<PII> segs;
void merge(vector<PII> &segs)
{
vector<PII> t;
sort(segs.begin(),segs.end());
int st = -1000000,ed = -1000000;
for(auto item : segs)
{
if(ed < item.first)
{
if(st != -1000000)t.push_back({st,ed});
st = item.first,ed = item.second;
}
else ed = max(ed,item.second);
}
if(st != -1000000)t.push_back({st,ed});
segs = t;
}
int main()
{
int n;
cin >> n;
while(n -- )
{
int l,r;
cin >> l >> r;
segs.push_back({l,r});
}
merge(segs);
cout << segs.size() << endl;
return 0;
}