第一章 基础算法(三) 双指针、位运算、离散化、区间合并

1 双指针算法

(一)两类双指针算法:

  1. 有两个指针分别指向两个序列;
  2. 有两个指针指向一个序列;

(二)双指针算法通用模板:

 for(i = 0,j = 0;i < n;i++)
 {
 	while(j < i && check(i,j))
 		j ++;
 	// 每道题目的具体逻辑
 }
 

(三)核心思想:
将如下算法,时间复杂度是O(n^2)

for(i = 0;i < n;i++)
	for(j = 0;j < n;j++)

优化到O(n)
(三)模板题:最长连续不重复子序列
题目:
给定一个长度为 n的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。

输入格式
第一行包含整数 n。
第二行包含 n个整数(均在 0∼10^5范围内),表示整数序列。

输出格式
共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。

输入样例:
5
1 2 2 3 5
输出样例:
3

【思路】:使用i,j两个指针,i指针从左到右遍历“ 1 2 2 3 5 ”,j指针尽可能指向最左端,使得 j ~ i 这段序列没有重复数字。 比如 i 指向 第一个数“1”,j最左可以指向第一个数“1”;i指向第二个数“2”,j最左可以指向第一个数“1”;i指向第三个数“2”,j最左可以指向第三个数“2”。随着 i 指针往后(右)走,j指针也往后(右)走,j 指针不会往前(左)走。

for(int i = 0,j = 0;i < n;i++)
{
	while(j <= i && check(j,i)) 
		j++;
	// check(j,i)是判断j和i之间有没有重复元素,有的话返回1
	res = max(res,i - j + 1)
	// i - j + 1 是从j到i这段子序列的长度
	// res 是连续不重复子序列最大长度
}

时间复杂度:O(n),i虽多从0走到n-1,j 同 i 。
check(j,i) 如何实现?开一个10万的数组s[N] , N = 100000 ,动态记录当前区间中每个数出现的次数。每次 i 往后移动一格相当于在区间(j,i)中加入了一个新的数,s[ a[i] ] ++; 每次 j 往后移动一格,相当于有一个数从区间(j,i)中出来了,s[ a[j] ] --;这样就可以动态统计出来区间中有多少个数。也可以用hash来实现。
【具体实现】

#include<iostream>
using namespace std;

const int N = 100010;
int a[N],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++)
	{
		s[a[i]] ++ ; // 区间(j,i)中新加入一个数 
	
		while(s[a[i]] > 1) // j 处于尽可能左端 
		{
			s[a[j]] --;   // s 数组中记录着区间a (j,i)中有哪些数(对应s[x]不为0), 这些数分别有多少个(对应s[x]的值为多少) 
			j++;
		}
			
		res = max(res,i-j+1);
	}
	cout << res;
	return 0;
}

2 位运算

(一)n的二进制表示

#include<iostream>
using namespace std;

int main()
{
	int n = 10; // 1 0 1 0
	for(int k = 3;k >= 0;k--)
		cout << (n >> k & 1); // 输出1010 
	return 0;
}

(二)lowbit(x)
举例:
x = 1010 lowbit(x) = 10 (保留x的最后一位1)
x = 101000 lowbit(x) = 1000
实现:
lowbit(x) = x & (-x) ; x 与负x
-x = ~x + 1
lowbit(x) = x & ( ~ x + 1)
应用:统计x中1的个数
每次把x最后一个1去掉,计算减到0减了多少次,x中1的个数就有多少个。

题目:二进制中1的个数 
给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数。

输入格式
第一行包含整数 n。
第二行包含 n个整数,表示整个数列。

输出格式
共一行,包含 n个整数,其中的第 i 个数表示数列中的第 i 个数的
二进制表示中 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的最后一位1 
 		{
 			x -= lowbit(x);
 			res++;
		 }
 		cout << res << " ";
	 }
	 
	return 0;
}

3 离散化

题目:区间和
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。
现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。
接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r] 之间的所有数的和。

输入格式
第一行包含两个整数 n 和 m。
接下来 n 行,每行包含两个整数 x 和 c。
再接下来 m 行,每行包含两个整数 l 和 r。

输出格式
共 m 行,每行输出一个询问中所求的区间内数字和。

数据范围

−10^9 ≤ x ≤ 10^9,
1 ≤ n,m≤ 10^5,10^9 ≤ l ≤ r ≤ 10^9,10000 ≤ c ≤10000

【分析】最多会用到 n + 2 * m(3 * 10^5)个坐标。这就是离散化问题的经典性质:整个值域跨度很大,但是非常稀疏,总共的范围是2 * 10^9,但是我们只用到了3 * 10^5,解决方法:把我们用到的下标映射到从1开始的自然数。比如我们现在想让x位置上的数加c,把x映射到k,a[k] += c,在求 l ~ r 之间的和时,先将l,r映射到kl,kr,再求 a[kl] ~ a[kr] 之间的和。

#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

typedef pair<int,int> Pair;

const int N = 300010;

int n,m;
int a[N],s[N];

vector<int> alls; // alls 存的是所有要离散化的值 
vector<Pair> add,query;

int find(int x) //  find 函数:求 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;
	} // 这个 while 就是求 x 在alls中的下标 
	return r + 1; 
	// 把所有的数映射到从1开始的数,因为下标是从0开始的,所以要加1
	 
 } 
 
 
 	/*
	unique 在 java 中没有,它可以怎样实现 
	对于一个排好序的序列,怎样保证这个序列没有重复的数
	要使每个数都满足 ①或 ② 
	①它是这个序列的第一个数
	②它与它前一个数不相等 
	*/
	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[i]不是第一个数(i不是0)或者a[i] 与 a[i-1] 相等
				a[j++] = a[i];
		 		
		}
		// a[0] ~ a[j-1] 是所有a中不重复的数 ,j就像一个新数组的指标一样 
		return a.begin() + j;
	}
	
 
 
int main()
{
	
	cin >> n >> m;  
	for(int i = 0;i < n;i++)
	{
		int x,c;
		cin >> x >> c;
		add.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);
	}
	
	// all 去重:①排序 ②将重复元素去掉
	sort(alls.begin(),alls.end());
//	alls.erase(unique(alls.begin(),alls.end()),alls.end());
	alls.erase(unique(alls),alls.end());
	

	
	for(auto i:add) //  给x位置处的值加上c 
	{
		int x = find(i.first);
		a[x] += i.second;
		// item.first 是位置, item.second是要加上的值 
	}
	
	// 预处理前缀和
	for(int i = 1;i <= alls.size();i++) 
		s[i] = s[i-1] + a[i];
	
	for(auto i:query)
	{
		int l = find(i.first),r = find(i.second);
		cout << s[r] - s[l - 1] << endl;
	}
	
	return 0;
}

4 区间合并

题目:
给定 n 个区间 [li,ri],要求合并所有有交集的区间。
注意如果在端点处相交,也算有交集。
输出合并完成后的区间个数。

例如:[1,3][2,6] 可以合并为一个区间 [1,6]。

输入格式
第一行包含整数 n。
接下来 n行,每行包含两个整数 l 和 r。

输出格式
共一行,包含一个整数,表示合并区间完成后的区间个数。

输入样例:
5
1 2
2 4
5 6
7 8
7 9
输出样例:
3

解决方法:
(1)按区间左端点进行排序;
(2)因为已经按左端点排序了,如果当前维护的区间和下一个区间没有交集,就可以将当前维护的区间放到答案中去了。

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
	
typedef pair<int,int> Pair;
const int N = 100010;
int n;
vector<Pair> segs;

void merge(vector<Pair> &segs)
{
	vector<Pair> res;
	sort(segs.begin(),segs.end()); // pair 排序优先按第一个数排
	int st = -2e9,ed = -2e9; // st 和 en 是 当前维护的区间的左右端点 
	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; // 更新区间 
	
}

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值