双指针+位运算+离散化+区间合并模板(acwing)

224 阅读7分钟

2816. 判断子序列 - AcWing题库高质量的算法题库https://www.acwing.com/problem/content/description/2818/


目录

双指针

例题1:

给出一行带有空格的字符串,求出里面的单词

例题2:

给定一个长度为 n nn 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度

例题3:

例题4:判断子序列

位运算

例题1:n的二进制表示中第k位是几

 例题2:lowbit(x):返回x的最后一位1

 例题3:二进制中1的个数

离散化

例题1:区间和

区间合并

例题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;
}