2816. 判断子序列 - AcWing题库高质量的算法题库
https://www.acwing.com/problem/content/description/2818/
目录
给定一个长度为 n nn 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度
例题1:合并区间
双指针
一般暴力方法:时间复杂度为O(N^2)
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (check(j, i))
{
}
}
}
双指模板:时间复杂度为O(N)
for (int i = 0, j = 0; i < n; i++)
{
while (j < i && check(j, i))
{
j++;
}
}
例题1:
给出一行带有空格的字符串,求出里面的单词
#define _CRT_SECURE_NO_WARNINGS
#include<vector>
#include <string>
#include<stdlib.h>
#include<math.h>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<string.h>
using namespace std;
int main()
{
char str[1000];
gets_s(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;
}
例题2:
给定一个长度为 n nn 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度
暴力做法:
for (int i = 0; i < nums.size(); i++)
{
for (int j = 0; j <= i; j++)
{
if (check(j, i))
{
res = max(res, j - i + 1);
}
}
}
双指针做法:
#define _CRT_SECURE_NO_WARNINGS
#include<vector>
#include <string>
#include<stdlib.h>
#include<algorithm>
#include<iostream>
#include<string.h>
using namespace std;
const int N = 10010;
int n;
int a[N], s[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++)
{
s[a[i]]++;
while (s[a[i]] > 1)
{
s[a[j]]--;
j++;
}
res = max(res, i - j + 1);
}
cout << res << endl;
return 0;
}
例题3:
给定两个升序排序的有序数组 A 和 B ,以及一个目标值
请你求出满足 A [ i ] + B [ j ] = x 的数对 ( i , j )
暴力做法:
for(int i=0;i<nums.size()-1;i++)
for(int j=i+1;j<nums.size();j++)
if(nums[i]+nums[j]==target)
return {i,j};
双指针做法:
#define _CRT_SECURE_NO_WARNINGS
#include<vector>
#include <string>
#include<stdlib.h>
#include<algorithm>
#include<iostream>
#include<string.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
ll a[N], b[N];
int main()
{
int n, m, x;
cin >> n >> m >> x;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= m; i++) cin >> b[i];
sort(a, a + n);
sort(b, b + m);
int ansi = 0;
int ansj = 0;
for (int i = 1, j = m; i <= n && j >= 1; i++)
{
while (a[i] + b[i] > x) j--;
if (a[i] + b[j] == x)
{
ansi = i;
ansj = j;
break;
}
}
cout << ansi - 1 << " " << ansj - 1;
return 0;
}
例题四:判断子序列
给定一个长度为 nn 的整数序列 a1,a2,…,ana1,a2,…,an 以及一个长度为 mm 的整数序列 b1,b2,…,bmb1,b2,…,bm。
请你判断 aa 序列是否为 bb 序列的子序列。
子序列指序列的一部分项按原有次序排列而得的序列,例如序列 {a1,a3,a5}{a1,a3,a5} 是序列 {a1,a2,a3,a4,a5}{a1,a2,a3,a4,a5} 的一个子序列。
双指针做法: (暴力的方法没法判断有序)
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int main()
{
int n, m;
cin >> n >> m;
for (int i = 0; i < n; i++) cin >> a[i];
for (int i = 0; i < m; i++) cin >> b[i];
int i = 0;
for (int j = 0; j < m; j++)
{
if (i < n && a[i] == b[j]) i++;
}
if (i == n) cout << "Yes";
else cout << "No";
return 0;
}
位运算
例题1:n的二进制表示中第k位是几
步骤:
(1) 先把第k位移到最后一位 n > > = k
(2)看个位是几n & 1
例题2:lowbit(x):返回x的最后一位1
x = 1010 lowbit(x) = 10
x = 101000 lowbit(x) = 1000
实际操作:x&(-x)=x&(x+1)
例题3:二进制中1的个数
问题:给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数
普通移位与1&法:
#include<iostream>
using namespace std;
int main()
{
int n;
cin >> n;
while (n--)
{
int x;
cin >> x;
int res = 0;
while (x)
{
int t = x & 1;
if (t) res++;
x = x >> 1;
}
cout << res << " ";
}
return 0;
}
调用lowbit法:
#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), res++;
cout << res << " ";
}
return 0;
}
离散化
应用:将大的数组存储数值范围映射到小数组
步骤:
(1)a[i]中有重复数字需要去重
(2)用二分法算出离散化的值x
模板:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
vector<int>alls;//存储所有待离散化的值
sort(alls.begin(), alls.end());//排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());//去重
int find(int x)//找到第一个大于等于x的位置
{
int left = 0;
int right = allls.size() - 1;
while (left < right)
{
int mid = left + (right - left) / 2;
if (alls[mid] >= x) right = mid;
else left = mid + 1;
}
return r + 1;//映射到1,2,3,,,,,n需要+1
}
例题1:区间和
问题:进行n次操作,每次操作将某一位置x加上c,接下来进行m次询问,询问[left,right]区间内的所有数的和
难点在于搞清楚每个数组到底在干什么:
p:存储{位置x,常数c},为了实现a数组离散化做铺垫
query:存储{left,right},目的是为了最后一步输出区间内的区间和,提供边界
alls:存储目标位置x,并将x进行排序去重,目的是为了a数组提供alls.size(),并且下标对应
即将原本的 [1,3,2,4,5,1,2,1,1] --->>> [1,2,3,4,5]
至于次数减少了,这是它的副作用,但是没有关系,p数组已经存储了次数
a:最关键的数组,p,alls数组都是为了构造a数组而诞生的,它实现了离散化
sum:是为了求出最后的结果,减少时间复杂度而诞生的前缀和数组
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int, int> pii;
vector<pii>p;
vector<pii>query;
vector<int> alls;
const int N = 30005;
int find(int x)//找到第一个大于等于x的位置
{
int left = 0;
int right = alls.size() - 1;
while (left < right)
{
int mid = left + (right - left) / 2;
if (alls[mid] >= x) right = mid;
else left = mid + 1;
}
return right + 1;//映射+1
}
int a[N], sum[N];
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)//n次操作
{
int x, c;
cin >> x >> c;
alls.push_back(x);//尾插位置:存储所有待离散的值
p.push_back({ x,c });//尾插{位置,常数}
}
for (int i = 1; i <= m; i++)//m次询问
{
int left = 0;
int right = 0;
cin >> left >> right;
alls.push_back(left);//尾插左端点
alls.push_back(right);//尾插右端点
query.push_back({ left,right });//在询问数组中尾插{左端点,右端点}
}
//alls数组存放的是所有要加c的目标位置x,因为是先操作完,再进行询问,位置不需要重复
sort(alls.begin(), alls.end());//排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());//去重
for (auto item : p)//p是存放的{x,c}
{
a[find(item.first)] += item.second;//映射到a数组:将p数组中x的位置映射a数组的新位置:right+1
//并在改位置上+原本对应位置上的存储的值
}//a一开始未进行任何操作,所以其任何位置上的数都是0
for (int i = 1; i <= alls.size(); i++) sum[i] = sum[i - 1] + a[i];//经过了离散化alls与a的下标一一对应
//将sum数组作为前缀和数组
for (auto item : query)//遍历询问数组
{
int left = find(item.first);//赋值操作
int right = find(item.second);
cout << sum[right] - sum[left - 1] << endl;//前缀和
}
return 0;
}
区间合并
例题1:
n表示区间的个数,[left,right]表示边界,给出n个区间,输出它们合并后的区间个数
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int, int>PII;
const int N = 10010;
int n;
vector<PII>segs;
void merge(vector<PII>& segs)
{
vector<PII>res;
sort(segs.begin(), segs.end());//按第一个关键字进行排序
int st = -2e9;
int 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 });//特殊情况:如果中间有一个非常长的尾,
//无人能敌,那么最后将这个没有人降伏的也收归res
segs = res;
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
{
int left = 0;
int right = 0;
cin >> left >> right;
segs.push_back({ left,right });
}
merge(segs);
cout << segs.size() << endl;
return 0;
}