Content:
双指针算法
位运算
离散化
区间合并
Part 1 双指针算法(降低时间复杂度)
双指针算法的用法在之前已经不止一次提到过了,对于双指针,最需要注意的一点就是如何确定下列代码中的check函数以及具体问题的逻辑:
//for-while模板
for (int i = 0, j = 0; i < n; i ++ )
{
while (j < i && check(i, j)) j ++ ;
// 具体问题的逻辑
}
例题,给定一个长度为n的整数序列,找出最长的不包含重复数字的连续子序列,并输出其长度:
令其中一个指针从起点开始向右走,当发现与起点数字相同时停止,记下当前距离记为res,这样只需要不断改变起点位置,再对res值进行比较即可,不需要双循环。
#include<iostream>
#include<vector>
using namespace std;
const int N = 10010;
int main()
{
int res = 0;
string a;
cin >> a;
for (int i = 0; i < a.size(); i++) {//i每次向后移动一位,i后j前
int j = 0;
while (a[i] != a[j] && j<=i) {
j++;//观察j最远能到达哪里
}
res = max(res, i - j + 1);
}
cout << res << endl;
return 0;
}
Part 2 位运算
求n的第k位数字:n>>k & 1
返回n的最后一位数字1:lowbit(n)=n & -n(一个二进制数,最高位是1,其余全是0)
原理:n & -n=n &(~n+1)
例题,求出一串数字中每个数字二进制中1的个数
代码实现:
#include<iostream>
#include<vector>
using namespace std;
int lowbit(int x)
{
return x & -x;
}
int main()
{
int n;
cin >> n;
while (n--)
{
int x;
cin >> x;
int res = 0;
while (x) x -= lowbit(x),res++;//每次减去x的最后一位1
cout << res << endl;
}
return 0;
}
Part 3 离散化
离散化的思想核心是将一个极大数组中的元素快速映射到一个个小型数组中,原因是原先数组中的元素数量并不多,但是值域会非常大,因此需要通过一些操作来减小数组的开辟空间(否则会浪费大量空间)。这些小数组中的元素是有可能重复的,因此需要进行去重。而想要快速算得这些数组在离散化后的值,就需要用到二分。
基本的步骤可以分为:
1、用一个辅助的数组把你要离散的所有数据存下来。
2、排序,排序是为了后面的二分。
3、去重,因为我们要保证相同的元素离散化后数字相同。
4、索引,再用二分把离散化后的数字放回原数组。
实现代码:
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
}
例题,给定一个无限长的数轴,数轴上每个坐标的数据均为0。现在,首先进行n次操作,每次操作将某一位置x上的数加c。接下来,进行m次询问,每个询问包含两个整数l和r,求出在区间【l,r】之间的所有数的和。
Input:line 1:n,m
line n:每行包含x,c
line m:每行包含l,r
核心思想:将所有用到的坐标借助一定的方式映射到数轴上新的位置。
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 300010;
int n, m;
int a[N], s[N];
typedef pair<int, int> PII;
vector<int> alls;
vector<PII> add, query;
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;
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i++)
{
int x, c;
cin >> x >> c;
add.push_back({ x,c });
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)
{
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[r - 1] << endl;
}
return 0;
}
如果没有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];
return a.begin() + j;
}
Part 4 区间合并
区间合并即将有交集的多个区间合并为一个完整的区间,最后返回合并后的区间个数:
按区间左端点进行排序;
扫描整个区间,将可能有交集的区间相合并;
每个区间只扫描一遍,因此该题并不需要双指针算法
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int, int>PII;
const int N = 100010;
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 });
segs = res;
}
int main()
{
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;
}
题目来源&代码参考:ACWing
感谢大家的支持,博主会继续提升文章质量,大家对文章中任何讲述不明的地方都可以留言哈,我们共同探讨,一起进步!