双指针算法
核心思想: 将时间复杂度为O(N^2)的朴素算法优化到O(N)
核心代码:
for(int i = 0,j = 0;i < n;i ++ )
{
while(j < i && check( (i,j) ) j ++ ;
// 具体的每道题目的基本逻辑:
}
模版:
对于一个给定的字符串 , 其中每个单词间隔1个空格,将它们分割后输出
code:
// 给定一个字符串 将其中的单词分割输出
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010; char c[N];
int main()
{
gets(c);
int len = strlen(c);
for(int i = 0, j = 0;i < len; i ++ )
{
j = i;
while(j < len && c[j] != ' ') j ++ ;
for(int k = i;k < j;k ++ ) printf("%c",c[k]);
cout << endl;
i = j;
}
return 0;
}
模版二:
给定一个序列,求出其最长连续不重复子序列的长度
思路 : 双指针 i , j
暴力做法:
for(int i = 0;i < n;i ++ )
for(int j = 0;j < i;j ++ )
if(check(i,j)) // 相应的操作
res = max (res, i - j + 1);
因为其是单调的 , 可以优化到O(N)
代码如下:
注意: 此代码开了一个数组存放数列中元素出现的数的次数,所以仅仅限于数据量较小的情况,当数据量很大的时候 需要用到 Hashmap 哈希表来进行操作
// 给定一个序列 求其最长连续不重复子序列的长度
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int a[N] , s[N]; int n;
int main()
{
scanf("%d",&n);
for(int i = 0;i < n; i++ ) scanf("%d",&a[i]);
int res = 0;
for(int i = 0 , j = 0;i < n;i ++ )
{
s[a[i]] ++ ;
while(s[a[i]] > 1)
{
s[a[j]] -- ;
j ++ ;
}
res = max(res,i - j + 1);
}
cout << res << endl;
return 0;
}
位运算
十进制 -> 二进制 取余
二进制 -> 十进制 每位*2^n (从 0 开始)
补码的计算公式:
正数:源码、反码和补码都相同。
负数:补码 = 反码(符号位保持不变) + 1
注意: 负数在计算补码的时候,在源码取反的过程中要保留符号位不变,其他位取反,例如:10001010取反11110101(第一个1不变),补码就为11110110。
模版一:
求 x 的二进制表示中的第 k 位是几
代码如下:
// 求x的二进制表示中第k位数是多少
#include <iostream>
using namespace std;
int main()
{
int x , k ; scanf("%d%d",&x,&k);
cout << (x >> k & 1) << endl;
return 0;
}
模版二:
树状数组的基本操作 : lowbit(X)
效果 : 返回 X 的最后一位 1 (注意返回的是二进制数)
实现 : x & -x
涉及原理 : 二进制的源码 补码 反码
应用一: 求 x 二进制中 1 的个数
代码如下:
// 求 x 二进制中的 1 的个数是多少
#include <iostream>
using namespace std;
int lowbit(int x)
{
return x & -x;
}
int main()
{
int n ; scanf("%d",&n);
while (n -- )
{
int x ; scanf("%d",&x);
int cnt = 0;
while (x) x -= lowbit(x), cnt ++ ;
cout << cnt << " " << endl;
}
cout << endl;
return 0;
}
离散化
整数 . 有序
当题目的 整个值域跨度很大 但其值分布的很稀疏的时候, 可以使用离散化 对存储空间进行优化 防止爆栈
- a [ ] 中可能重复的元素 – 去重
- 算出 x 离散化后的值 – 二分
模版 :
vector<int> alls ; // 存储所有待离散化的值
sort(alls.begin() , alls.end()) // 将所有值排序
alls.erase(unique(alls.begin() , alls.end()) , alls.end()) ; // 去重
// 二分求出 x 对应的离散化的值
int find(int x)
{
int l = 0 , r = all.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;
}
模版例题:
一个无限长的数轴 , 初始时所有点对应的值都是 0
现有如下操作 , 对任意一个点的数 + c ,
有如下询问 , 求任意区间的区间和
现在 经过 m 次操作 q 次询问 输出答案
代码如下:
注意:因为这里查询操作中的数也要一起离散化 , 所以需要将其预先存储到一个pair容器中 , 不能边读入输入的数据边输出.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int,int> PII;
const int N = 300010;
int n , m ;
int a[N] , s[N] ;
vector<int> alls;
vector<PII> add , query ;
// find 函数 求出 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}); // 注意 pair的输入要使用 {}
alls.push_back(x) ;
}
for(int i = 0;i < m;i ++ )
{
int l , r;
cin >> l >> r ;
query.push_back({
l,r});
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) // c++ 11 的新特性 可由auto : 遍历容器
{
int x = find(item.first) ;
a[x] += item.second ;
}
// 预处理前缀和 :
for(int i = 1 ; i <= alls.size() ; i ++ ) s[i] = s[i - 1] + a[i] ;
// 处理询问 :
for(auto item : query)
{
int l = find(item.first) , r = find(item.second) ;
cout << s[r] - s[l - 1] << endl;
}
return 0;
}
区间合并
给定 多个 区间 , 把所有有交集的区间合并成一个 区间
- 按区间的左端点进行排序
- 按区间从左右往右扫描 , 每次维护一个区间 // 左端点从小到大
c++中的 sort 排序
-> sort排序pair时 先看first的优先级 再看second的优先级 , 即在此左端点 会优先于 右端点
模版 :
对于给定的区间 , 将有交集的区间合并成一个区间 , 求合并后区间的个数 :
代码如下:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 100010 ;
typedef pair<int,int> PII ;
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 = seg.first , ed = seg.second; // 当 当前维护的区间与离其最近的右边的区间无交集的时候
// 对当前维护的端点进行更新
}
else ed = max(ed,seg.second); // 区间合并 左端点不变右端点更新为最右边的端点
}
if(st != -2e9) res.push_back({
st,ed}) ; // //此处的if判断 防止输入的数组segs里面是空的 即输入的 n 为0的情况
// 如果为空 则不修改
segs = res ;
}
int main()
{
scanf("%d",&n) ;
for(int i = 0;i < n;i ++ )
{
int l , r;
scanf("%d%d",&l,&r);
segs.push_back({
l,r}) ;
}
merge(segs) ;
cout << segs.size() << endl;
return 0;
}