AcWing 算法基础课第三节基础算法3 双指针、位运算、离散化、区间合并
1、该系列为ACWing中算法基础课,已购买正版,课程作者为yxc
2、y总培训真的是业界良心,大家有时间可以报一下
3、为啥写在这儿,问就是oneNote的内存不够了QAQ
ACwing C++ 算法笔记3 基础算法
- 一、双指针算法
- 1.1 双指针的类型
- 1.2 双指针写法通用模板
- 二、位运算
- 2.1 求n的第k位数字
- 2.2 返回n的最后一位1
- 2.3 原码反码补码相关知识
- 三、离散化
- 3.1 离散化的基本含义
- 3.2 离散化的步骤
- 补充,实现unique函数
- 四、区间和并
- 4.1 区间合并的含义
- 4.1 区间合并的步骤
本节内容:双指针、位运算、离散化、区间合并
一、双指针算法
1.1 双指针的类型
- 双指针指向两个序列
对于两个序列,维护某种次序,例如归并排序合并两个有序序列的操作运用的就是双指针算法。
- 双指针指向一个序列(大多数)
对于一个序列,用两个指针维护一段区间。例如,快排在划分区间时,两个指针维护一个区间
1.2 双指针写法通用模板
for(i=0, j=0; i<n; i++)
{while(j<i && check(i,j)) j++;// 每道题目的具体逻辑
}
虽然看起来是两重循环,但是每一个指针在所有循环里面移动次数不超过N,双指针则不超过2N。
双指针算法最核心的性质:优化,将O(N^2)的复杂度优化为O(N)。
未优化
for(int i=0; i<n;i++)for (int j=0; j<n; j++)
- 举例一:输入一个字符串 abc edf ghk,再将每个单词分别输出出来。
#include <iostream>
#include <string.h>using namespace std;int main()
{char str[1000];gets(str);// gets因为不会限制读入字符数量,因此被禁用,应使用fgets(名字,大小,stdin);int n = strlen(str);for(int i=0; i<n; i++){// 希望j停留在单词的最后一个字母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;
}
- 举例二:最长连续不重复子序列。给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
朴素做法 O(N^2)
for (int i = 0; i < n; i ++ )for (int j = 0; j < i; j ++ )if(check(j,i)){res= max(res, i-j+1);}
双指针做法:红色箭头i
遍历,绿色箭头j
放在字符串不重复的最远的地方。由于指针具有单调性(随着i
向后移动,j
一定向后移动),可以优化代码。
for (int i = 0, j=0; i < n; i ++ )while (j<=i && check(j,i)) j++; // 有重复元素res = max(res, i-j+1);
判断是否有重复数字的方法:维护一个数组s[N]
,i
向右移就加数字s[a[i]]++
,j
向右移就减数字s[a[j]]--
,动态统计区间有多少数。如果新加的有重复元素,那么这个重复元素一定是a[i]
,因此check(j,i)
可以简写为a[j] != a[i]
。完整代码如下
#include <iostream>using namespace std;const int N = 100010;
int a[N];
int s[N];
int n;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 ++ ) // 不用写j<=i因为,当j>i时不满足s[a[i]]>1 ,区间里没有数,一定满足要求没有重复的数);{s[a[i]]++;while (s[a[i]]>1) // 当有重复的数字时,一直找到a[i]==a[j]{ s[a[j]] --; j++;}res = max(res, i-j+1);}cout << res << endl;return 0;
}
- 做题思路:先写出暴力方法的模板,再看
i,j
之间是否有单调关系,再进行优化
二、位运算
本节介绍位运算的常用操作
2.1 求n的第k位数字
n的二级制表示中第k位是几(个位是第0位)。
- 位运算的基本思路:
- 先把第
k
位数字移到最后一位n >> k
- 看个位是几
x & 1
- 先把第
- 得到公式:
n>>k &1
- 代码:
#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; }
2.2 返回n的最后一位1
lowbit
操作是树状数组的基本操作之一,作用是返回n的最后一位1。
lowbit
是如何实现的:一个整数的负数是原数的补码(补码是取反加一),即-x = ~x+1
,x&-x = x & (~x+1)
- 图解:
lowbit(n) = n & -n
- 应用:统计n里面1的个数
#include <iostream>using namespace std;// 给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数。
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的最后一位1cout << res << " ";}return 0;
}
2.3 原码反码补码相关知识
设x =1010
- 原码:
0...01010
- 反码:
1...10101
- 补码:
1...10110
- 由于计算机的底层实现是没有减法的,而在数学上负数具有性质
-x = 0-x
。而0
在做减法时需要借位由0...0
变为10...00
,因此用补码来表示负数。
三、离散化
这里特指整数的离散化。
3.1 离散化的基本含义
一组数,数的范围特别大(0-10^9
),但个数少(10^5
),有些题目我们需要将这些值作为下标,但是我们很难开一个10^9
的数组。因此我们将这个序列映射到从0
开始的连续的自然数。
- 例如:
这样的映射过程就被称为离散化(而且是保序的1)。
离散化中的问题:
- 1、
a[]
数组中可能有重复元素,需要去重; - 2、如何算出
a[i]
离散化后的值是多少,或找到数x
在a[]
中的下标。(因为a是有序的,可以用二分法);
- 1、
3.2 离散化的步骤
- 第一步:排序去重,这些数字排好序的下标的就是映射后的值。常见写法如下(用
vector
进行离散化,java中用ArrayList
):
vector<int> alls; // 存储所有待离散化的值,假设alls是a[]数组
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素
// unique() 将所有重复元素去重,并返回去重后不重复位置末端点
// erase() 删掉重复的元素
例如:
原数组:[1, 2, 100, 2000, 30000]
映射后:[0, 1, 2, 3, 4]
- 第二步:离散化,从0到数组n-1,找到x的位置
// 二分求出x对应的离散化的值
// 找到第一个大于等于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,2,...,n,不加一从0,...n-1的映射// r是否加一与题目有关
}
- 举例:区间和
- 假定有一个无限长的数轴,数轴上每个坐标上的数都是
0
。现在,我们首先进行n
次操作,每次操作将某一位置x
上的数加c
。接下来,进行m
次询问,每个询问包含两个整数l
和r
,你需要求出在区间[l,r]
之间的所有数的和。
- 如果数据范围小(
10^5
),可以采用前缀和的方法,但本题是【10^-9
——10^9
】 且涉及到的数的个数很少(相加只用到n
个x
下标,查询只用到2m
个下标,总共在2x10^9
范围内只用到了3x10^5
个数)。 - 将所有用到的下标拿过来,映射到从1开始的自然数。如果
x
离散化之后是k
,就让a[k]+=c
,再求前缀和。
#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> adds, query;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开始 }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 < 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:adds){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);int r = find(item.second);cout << s[r] - s[l-1] << endl;}return 0; }
- 假定有一个无限长的数轴,数轴上每个坐标上的数都是
补充,实现unique函数
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]; //满足这个性质就存到数组前面// a[0]——a[j-1]是所有不重复的数return a.begin()+j;
}
// 去重
sort(alls.begin(), alls.end());
alls.erase(unique(alls), alls.end());
四、区间和并
4.1 区间合并的含义
- 给n个区间,把所有有交集的区间进行合并,输出合并后的区间个数。
- 注意两个区间端点相交也会合并为同一个区间。
- 区间情况:1、区间A包含区间B;2、区间A与B相交;3、区间A与B不相交。(排过序之后,不可能有区间B包含区间A,即不存在B在A的左边)
- 样例:
4.1 区间合并的步骤
- 按照所有区间的左端点排序
- 扫描整个区间,扫描中维护一个当前的区间,将有交集的区间合并。
-举例:给定 n 个区间[l,r]
,要求合并所有有交集的区间。注意如果在端点处相交,也算有交集。输出合并完成后的区间个数。例如:[1,3] 和 [2,6] 可以合并为一个区间 [1,6]。输出共一行,包含一个整数,表示合并区间完成后的区间个数。
#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()); // 优先以左端点排序,再以右端点排序;// 设置初始的负无穷到负无穷的边界值;// st代表区间开头,ed代表区间结尾,防止传空区间int st = -2e9, ed = -2e9;for (auto seg:segs){if(ed < seg.first) // 情况1:两个区间无法合并。当前区间在枚举区间的左边,没有交集,说明找到了一个完整的区间{if(st!=-2e9) res.push_back({st, ed}); // 防止初始值被记录,区间1放进res数组st = seg.first; // 维护区间2ed = seg.second;}else // 情况2:两个区间可以合并,且区间1不包含区间2,区间2不包含区间1。有交集,更新右端点。// 或情况3:区间1包含区间2,此时不需要任何操作,可以省略。{ed = max(ed, seg.second); // 区间合并}}// 1、防止输入区间为空,即n=0,此时区间个数为1// 2、防止没有合并循环结束时的最后一个区间{st, ed}if (st != -2e9) res.push_back({st, ed});segs = res; // 区间更新为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 算法基础课第三节基础算法3 双指针、位运算、离散化、区间合并相关推荐
- 基础算法(三):双指针/位运算/离散化/区间合并
目录 1.双指针算法 引例 最长连续不重复子序列 2.位运算 n的二进制表示中第k位是几 lowbit(x)操作:返回x二进制表示中的最后一位1 3.离散化 4.区间合并 1.双指针算法 引例 输入一 ...
- AcWing 算法基础课第一节基础算法1排序、二分
1.该系列为ACWing中算法基础课,已购买正版,课程作者为yxc 2.y总培训真的是业界良心,大家有时间可以报一下 3.为啥写在这儿,问就是oneNote的内存不够了QAQ ACwing C++ 算 ...
- 人工智能算法:卷1基础算法+卷2受大自然启发的算法+卷3深度学习和神经网络电子书
ISBN:9787115005786 包装:平装 字数:538000 页数:598 版次:7 开本:16开 用纸:胶版纸 正文语种:中文 人工智能算法:卷1基础算法+卷2受大自然启发的算法+卷3深度学 ...
- (ACWing yxc讲解基础算法课程笔记)基础算法 整数二分
二分排序: 整数二分: 我们先来说一下单调性和二分的区别,有单调性一定能进行二分排序,但是能进行二分排序的不一定有单调性.所以说二分的本质并非是单调性. 我们假设有这样一个范围.我们能把它分成两个部分 ...
- 【AcWing 学习】基础算法
AcWing 基础算法 排序 快速排序 归并排序 堆排序 冒泡排序 选择排序 插入排序 希尔排序 计数排序 桶排序 基数排序 二分 整数二分 浮点数二分 高精度 高精度加法 高精度减法 高精度乘法 高 ...
- Acwing算法基础课知识点
知识点 基础算法 -- 代码模板链接 常用代码模板1--基础算法 排序 二分 高精度 前缀和与差分 双指针算法 位运算 离散化 区间合并 数据结构 -- 代码模板链接 常用代码模板2--数据结构 链表 ...
- acwing基础算法
目录 第一章 Ⅰ.快速排序 Ⅱ.归并排序 Ⅲ.二分 有单调性的一定可以二分,但是二分不一定有单调性 整数二分 浮点数二分 Ⅳ.高精度 高精度加法: 高精度减法 高精度乘法 高精度除法 Ⅴ.前缀和与差分 ...
- Acwing算法基础课学习笔记
Acwing学习笔记 第一章 基础算法 快速排序 归并排序 二分查找 前缀和与差分 差分 位运算 离散化 第二章 数据结构 单链表 双链表 栈 队列 单调栈 单调队列 KMP算法 Trie 并查集 堆 ...
- 第一章 基础算法 【完结】
已经全部熟练掌握,还得经常的复习 目录 排序 二分 高精度 前缀和 差分 位运算 双指针 离散化 区间合并 排序 模板题 AcWing 785. 快速排序 786. 第k个数 快速排序 快排的核心思想 ...
最新文章
- Django学习笔记 开发环境搭建
- LeetCode Majority Element
- boost::contract模块实现friend功能的测试程序
- cvAdd()和 cvAddS()函数的使用
- 怎么批量修改html文件后缀,怎么批量修改文件后缀
- 论文页眉奇偶页不同怎么设置_怎样设置Word页眉页脚奇偶页不同?
- 投票选举 算法_区块链主流共识算法一文全通
- Java 线程之间通信
- 我想站在巨人的肩上——记成都之行
- python切片习题与详细讲解
- Windows Phone 8.1 多媒体(2):视频
- 如何把图片与压缩包合并成可改后缀名的图片文件及原理
- 用python来开发webgame服务端(2)
- vue之解决跨域问题
- 5.1.2全景声音箱摆位_5.1.2全景声系统私人家庭影院设计方案
- 飞秋等级授权码_观点 | 谈谈网络安全等级保护与密码法
- Easy Iot实现MQTT实验
- vue3获取url后面参数
- UneXt 基于MLP的快速医学图像分割网络
- 什么样的耳机戴着舒服些、最好用的的几款骨传导蓝牙耳机推荐