前言
重学算法第2天,希望能坚持打卡不间断,从基础课开始直到学完提高课。
预计时长三个月内,明天再来!肝就完了
2月14日,day02 打卡
今日已学完y总的
算法基础课-1.4-第一章 基础算法(三)
共4题,知识点如下
双指针算法: 最长连续不重复子序列
位运算:二进制中1的个数
离散化:区间和
区间合并:区间合并
(离散化和区间合并后期还需补补)
双指针算法
共两种形式
模板
for (int i = 0, j = 0; i < n; i ++ )
{
while (j < i && check(i, j)) j ++ ;
// 具体问题的逻辑
}
常见问题分类:
(1) 对于一个序列,用两个指针维护一段区间
(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
简单应用
例题1:输出并打印每个单词,各占一行
// 最简单的双指针算法应用,一维情况
#include <iostream>
#include <string.h>
using namespace std;
int main() {
char str[1000];
gets(str); // 好像需要使用fgets
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;
}
AcWIng 799. 最长连续不重复子序列
模板题
思路:
#include <iostream>
using namespace std;
const int N = 100010;
int n;
int a[N]; // 原数组
int s[N]; // 当前j和i区间内每个数出现的次数
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++) {
s[a[i]] ++;
while (s[a[i]] > 1) { // 新加的数重复了才走while
// 如果j和i区间内还是有重复的数,就把j对应的值剔除
s[a[j]] --; // 把j拿出去
j++;
}
res = max(res, i - j + 1);
}
cout << res << endl;
}
/*如下 i走了2步 j走了2步(因为ij区间内有重复,没重复的时候j不走)
1 2 2 3 5 ---> 1 2 2 3 5 --> 1 2 2 3 5 -----> 1 2 2 3 5
i i i i
j j j j
*/
总结:先写个暴力,然后看 i 和 j 之间有没有单调关系,有的话就用这个关系将算法从O(n2)降到O(n)
位运算
模板
求n的第k位数字: n >> k & 1
返回n的最后一位1:lowbit(n) = n & -n
看n的 第k位是几
#include <iostream>
#include <string.h>
using namespace std;
int main() {
int n = 10;
for (int k = 3; k >= 0; k--)
cout << (n >> k & 1);
return 0;
}
// 1010
返回 x 的最后一位1:lowbit(x)
-x = ~x + 1
x & -x = x &(~x + 1)
#include <iostream>
#include <string.h>
using namespace std;
int main() {
int n = 10;
unsigned int x = -n;
for (int k = 31; k >= 0; k--)
cout << (x >> k & 1);
return 0;
}
// 11111111111111111111111111110110
计算机用补码存数据
AcWing 801. 二进制中1的个数
实际应用,统计x(二进制)里面1的个数
思路:每次把最后一个1去掉,当x=0时,减了多少次就有多少1
#include <iostream>
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); // 每次减去x最后一位1
res++;
}
cout << res << " ";
}
return 0;
}
离散化
(特指整数)
模板
vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素
// unique 会将重复元素移到数组最后面,返回值是存放重复元素的起始坐标
// erase 删掉后半部分
// 二分求出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
}
AcWing 802. 区间和
思路:
数组下标较小时,可以用前缀和来做
a[x] += c
S[r] - S[l-1]
本题最多用 n+2m(l和r) 个坐标 — 3 x 10^5
由于值域很大,但里面的数很稀疏,要用到的只有他们间的相对关系,
每次要求数的和时,其实只用求L-R之间所有数的和即可,
跟数的绝对值无关,只与数的相对大小有关
所以可以把用到过的数映射成从1开始的自然数
映射完之后数在1 —3 x 10^5之间,可以用前缀和来解决
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 每种操作都是2个数,用pair来存
typedef pair<int, int> PII;
const int N = 300010;
int n, m;
int a[N], s[N]; // a[]: 存的数 s:[] 前缀和
vector<int> alls; // 离散化后的结果
vector<PII> add, query;
// 求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});// 下标x的位置加上c
//add.push_back(x, c);// 没加{}
alls.push_back(x); // 把x加到待离散化的数组里
}
for (int i = 0; i < m; i++) {
int l, r;
cin >> l >> r;
query.push_back({l ,r}); // 插入pair,需要用{}括起来
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); // 求x离散化后的结果
a[x] += item.second; // 在离散化后的位置上加上要加的数
}
// 预处理前缀和
for (int i = 1; i <= alls.size(); i++) s[i] = s[i - 1] + a[i];
// 处理询问
for (auto item : query) {
// item.first 左区间的值
int l = find(item.first), r = find(item.second); // 左右端点离散化后的值
// 中间所有数的个数
cout << s[r] - s[l- 1] << endl; // 前缀和公式
}
return 0;
}
离散化好像没完全懂,以后再补补
区间合并
只要有相交的部分就合并,哪怕只是端点相交
- 区间内部------不变
- 区间部分重合—更新ed
- 区间外----------将区间存入结果
模板
// 将所有存在交集的区间合并
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;
}
AcWing 803. 区间合并
思路:
#include <iostream>
#include <algorithm>
#include <vector>
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 != -2e9 :防止输入区间为空
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;
}
希望自己能坚持打卡不间断,学好算法和C++