1.3 基础算法(三)
这是我的一个算法网课学习记录,道阻且长,好好努力
1.3.1 双指针算法
模板
// 模板
for (i = 0, j = 0; i < n; i ++ )
{
while (j < i && check(i, j)) j ++ ;
//每道题具体逻辑
}
// 核心思想:将朴素的算法由O(n^2)优化到O(n)
例题1:输入一个字符串,将其中的每一个单词输出出来
#include <iostream>
#include <string.h>
using namespace std;
int main()
{
char str[1000];
gets(str);
int n = strlen(str);
for (int i = 0; i < n; i ++ )
{
int j = i;
while (j < n && str[j] != ' ') j ++ ;
// 这道题的具体逻辑
for (int k = i; k < j; k ++ ) cout << str[k];
cout << endl;
i = j;
}
return 0;
}
// 循环次数是小于2n的,因此将时间复杂度降低了
例题2:最长连续不重复子序列
#include <iostream>
using namespace std;
const int N = 100010;
int n;
int a[N], s[N]; // a[N] 整数序列 s[N] 表示当前j到i区间每一个数出现的次数
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> a[i]; // 读入n个数
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); // 更新res
}
cout << res << endl;
return 0;
}
// 注意 i指针在后面 j指针在前面
1.3.2 位运算
例题:二进制中1的个数
# include <iostream>
using namespace std;
int lowbit(int x) // 返回x的最后一位1
{
return x & -x; // x & -x == x & (~x + 1)
}
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 << ' ';
}
return 0;
}
1.3.3 离散化
例题:
无限长的数轴,数轴上每个坐标上的数都是0; n 次操作,每次操作将某一位置 x 上的数加 c;m 次询问,每个询问包含两个整数 l 和 r ,求出在区间 [l,r] 之间的所有数的和
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII; // pair 两个数据合并成一个数据(需要头文件vector)
const int N = 300010; // 开数组a,最坏的情况,下标和区间端点毫无重合,最大需要(n + 2m)的容量
int n, m;
int a[N], s[N];
vector<int> alls;
vector<PII> adds, 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; // 加1便于求前缀和(从1开始)
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i ++ )
{
int x, c;
cin >> x >> c;
adds.push_back({x, c}); // 存入加值
alls.push_back(x); // 存入下标
}
for (int i = 0; i < n; 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()); // 按大小顺序sort
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去重
for (auto item : adds)
{
int x = find(item.first); // 将数据存入a数组
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;
}
/*
// 手写unique函数
vector<int>:: iterator unique_m(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; // 非重复序列最后一个元素的下一个位置
*/
1.3.4 区间合并
例题:
给定 n 个区间 [li, ri],要求合并所有有交集的区间。
注意如果在端点处相交,也算有交集。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
vector<PII> segs;
void merge(vector<PII> &segs)
{
vector<PII> res;
// 排序 使区间有序
// 使用sort,先对左端点排序,当左端点相同的时候,对右端点排序
sort(segs.begin(), segs.end()); // 头文件algorithm
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; // 将结果返回给主函数引用的segs
}
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;
}