保研机试准备(经典算法篇)

未来的一个月我将不定期更新一些我的算法题目,主要是为了本人保研机试做准备。

本人主要熟悉的语言是python,但是由于保研机试基本要求要用C++,所以在未来的一个月中我将去学习一些C++的算法(大部分都是一些经典的算法),以应付机试要求。

本博客代码均为本人手写并AC。

第一次发博客,大佬勿喷。

直接进入正题:

目录

目录

动态规划

最长递增子序列长度

最长连续递增子序列长度

动态规划:

贪心算法:

最长递增子序列的个数

最长连续公共子序列

图论

并查集

二分法

整数序列

数的范围

筛素数

分解质因数

筛质数

埃氏筛

线性筛

DFS

全排列

N皇后问题

递进数字

二叉树的遍历

图的存储

图的深度优先遍历

树的重心

拓扑排序

基本算法

快速排序

拓扑排序

堆排序

前缀和

前缀和求每个字母的个数

差分

双指针算法

最长连续不重复子序列

位运算

离散化

全排列

数据结构

单链表

双链表

队列

单调栈

单调队列

KMP

思维题



动态规划

动规五部曲:
1.确定dp数组以及下标的含义
2.确定递推公式
3.dp数组初始化
4.确定遍历顺序
5.举例推导dp数组

最长递增子序列长度

//最长递增子序列
//dp数组:dp[i] = max(dp[j] + 1, dp[i]);
int lengthlis(vector<int>& nums) {
	if (nums.size() <= 1) return nums.size();
	vector<int> dp(nums.size(), 1);
	int result = 0;
	//dp[i]= max(dp[j]+1,dp[i]);
	for (int i = 1; i < nums.size(); i++) {
		for (int j = 1; j < i; j++) {
			if (nums[j] < nums[i]) {
				dp[i] = max(dp[j] + 1, dp[i]);
			}
		}
		if (result < dp[i]) result = dp[i];
	}
	return result;
}

最长连续递增子序列长度

动态规划:

注意:这里是连续递增子序列,只需要比较nums[i]和nums[i-1],所以只需要使用一层循环进行遍历即可。

//最长连续递增子序列
// 动态规划
//dp数组:dp[i] = dp[i - 1] + 1;
int lengthOfLIS(vector<int>& nums) {
	if (nums.size() == 0) return 0;
	//初始化dp数组和最终结果的值
	int result = 1;
	vector<int> dp(nums.size(), 1);
	// 遍历dp数组
	for (int i = 1 ; i < nums.size(); i++) {
		if (nums[i] > nums[i - 1]) {
			dp[i] = dp[i - 1] + 1;
		}
	//选出dp数组中最大的值
		if (dp[i] > result) result = dp[i];
	}
	return result;
}
贪心算法:

这道题目也可以用贪心来做,也就是遇到nums[i] > nums[i - 1]的情况,count就++,否则count为1(从头开始),记录count的最大值就可以了。

//最长连续递增子序列
// 贪心算法
//dp数组:dp[i] = dp[i - 1] + 1;
int lengthOfLIS(vector<int>& nums) {
	if (nums.size() == 0) return 0;
	//初始化dp数组和最终结果的值
	int result = 1;
	int count = 1;
	// 遍历给定数组
	for (int i = 1; i < nums.size(); i++) {
		if (nums[i] > nums[i - 1]) {
			count += 1;
		}
		else
		{
			//不连续,从头开始
			count = 1;
		}
		//选出dp数组中最大的值
		if (count>result)
		{
			result = count;
		}
	}
	return result;
}

不连续递增子序列的跟前0-i 个状态有关,连续递增的子序列只跟前一个状态有关。

最长递增子序列的个数

//最长连续递增子序列个数
// 动态规划
//dp数组:dp[i] = dp[i - 1] + 1;
//count数组更新分两种情况
int findNumberOfLIS(vector<int>& nums) {
	if (nums.size() <= 1) return nums.size();
	//初始化dp数组和count数组
	vector<int> dp(nums.size(), 1);
	vector<int> count(nums.size(), 1);
	//定义最大子序列长度
	int maxcount = 0;
	for (int i = 1; i < nums.size(); i++) {
		for (int j = 0; j < i; j++) {
			if (nums[i] > nums[j]) {
				//更新count数组
				if (dp[j] + 1 > dp[i]) {
					//dp[i] = dp[j] + 1;
					count[i] = count[j];
				}
				else if (dp[j] + 1 == dp[i])
				{
					count[i] += count[j];
				}
				//更新dp数组
				dp[i] = max(dp[i], dp[j] + 1);
			}
			//选出最大子序列长度
			if (maxcount < dp[i]) maxcount = dp[i];
		}
	}
	int result = 0;
	//选出长度等于最大子序列长度的子串的个数
	for (int i = 0; i < nums.size(); i++) {
		if (maxcount == dp[i]) result += count[i];
	}
	return result;
}

得出最大连续子序列个数的另一个思路是:跟上面求出最长递增子序列长度一样求出dp数组,然后找出dp数组中值最大的数的个数。这个想法具体没有写代码,但是感觉是对的,我也找了几个测试用例把dp数组打印出来对比了一下。

最长连续公共子序列

例题:3692. 最长连续公共子序列 - AcWing题库

这道题也可以使用动态规划思想去写,但是下面是一个偏暴力的方法,这个方法适合新手练习。主要的想法就是选取两个字符串中的一个,然后遍历这个串的所有子串,然后用find()方法在第二个子串中查找是否有该子串,最终找到最长连续公共子序列。

#include<iostream>
using namespace std;


int main() {
	int ans_1 = 0;
	string s1, s2,ans_2 = "";
	//string s1 = "abcde";
	cin >> s1 >> s2;
	string res = "";
	for (int i = 0; i < s1.length()-1; i++) {
		for (int j = i+1; j <s1.length(); j++) {
			string s3 = "";
			for (int k = i; k <=j ; k++) {
				s3 += s1[k];
			}
			//cout << s3 << endl;
			if (s3.length() >= ans_2.length()) {
				if (s2.find(s3) != -1 && s3.length() <= s2.length()) {
					ans_1 = s3.length();
					ans_2 = s3;
				}
			}
		}
		
	}
	cout << ans_1<<endl;
	cout << ans_2 << endl;



	return 0;
}

图论

并查集

例题1:剑指 Offer II 116. 省份数量 - 力扣(LeetCode)

//用来查找当前节点的根节点
//利用递归的思想
int Find(vector<int>& parent, int index) {
    if (parent[index] != index) {
        parent[index] = Find(parent, parent[index]);
    }
    return parent[index];
}

//将两个根节点进行合并
void Union(vector<int>& parent, int index1, int index2) {
    int x = Find(parent, index1);
    int y = Find(parent, index2);
    parent[x] = y;
}

//主函数
int findCircleNum(vector<vector<int>>& isConnected) {
    int cities = isConnected.size();
    //用来保存当前节点的父节点
    vector<int> parent(cities);
    //初始化parent,表示当前节点的父节点是本身
    for (int i = 0; i < parent.size(); i++) {
        parent[i] = i;
    }
    //遍历邻接矩阵,如果两个节点相连就将两个节点的根节点合并
    for (int i = 0; i < cities; i++) {
        for(int j = 0; j < cities; j++ ){
            if (isConnected[i][j] == 1) {
                Union(parent, i, j);
            }
        }
    }
    //求出不相连的根节点的个数
    int provinces = 0;
    for (int i = 0; i < parent.size(); i++) {
        if (parent[i] == i) {
            provinces += 1;
        }
    }
    return provinces;
}

例题2:3719. 畅通工程 - AcWing题库

跟上一题思路相同,求出不相连的根节点的个数,然后减1就得到了需要添加的路的个数。在写的过程中需要注意循环遍历的次数,以及动态分配parent内存的大小,不然会报错。这一题也可以使用广度优先搜索和深度优先搜索解题,但是目前我还没学到这里,之后会补上的。

#include<iostream>
#include <vector>
using namespace std;


int Find(vector<int> &parent, int index) {
	if (parent[index] != index) {
		parent[index] = Find(parent, parent[index]);
	}
	return parent[index];
}

void Union(vector<int>&parent, int index1, int index2) {
	int x = Find(parent, index1);
	int y = Find(parent, index2);
	parent[x] = y;
}


int main() {
	int n, m = 0;
	cin >> n>>m;
	vector<int> parent(n+1);
	for (int i = 1; i <= parent.size(); i++) {
		parent[i] = i;
	}
	for (int i = 0; i < m; i++) {
		int a, b = 0;
		cin >> a >> b;
		Union(parent, a, b);
	
	}
	int cnt = 0;
	for (int i = 1; i < parent.size(); i++) {
		if (parent[i] == i) {
			cnt += 1;
		}
	}
	cout<<cnt-1<<endl;
}

二分法

二分法的两个模板:

 如果l=mid,则mid=(l+r+1)/2,使用模板一;

 如果r=mid,则mid=(l+r)/2,使用模板二;

整数序列

例题:3717. 整数序列 - AcWing题库

方法1:使用暴力的方法。大致思想就是先从1遍历到输入的整数作为首项,然后再遍历等差数列的数目n,使用求和公式:sn=a1n+(n*(n-1))/2即可求出。这个方法可以求出结果,但是有些用例会超时。都使用暴力了,还要什么自行车。

#include<iostream>
using namespace std;

int main() {
	int m;
	cin >> m;
	int i = 1;
	int sign = 0;
	for (i = 1; i < m; i++) {
		for (int j = 2; j < (m / 2); j++) {
			if ((i * j + (j * (j - 1)) / 2) == m) {
				int a = i;
				sign = 1;
				for (int k = 1; k <= j; k++) {
					cout << a<<" ";
					a += 1;
				}
				cout << endl;
			}
		}
	}
	if (sign==0) {
		cout << "NONE";
	}
	
	return 0;
}

方法2:使用二分法。这次使用的是等差数列的另一个求和公式sn=(a1+an)n/2。遍历首项,然后使用二分的方法找出尾项,最后输出结果。需要注意的是存储数据使用的类型,不然会溢出。

#include<iostream>
using namespace std;
//注意数据的类型
using ll = long long;


int main() {
	//二分法
	ll N;
	cin >> N;
	//设置信号量,用来判断是否有结果输出
	int sign = 0;
	//使用二分法寻找等差数列的最后一项
	for (int i = 1; i <=N / 2; i++) {
		ll l = i + 1, r = N / 2 + 1;
		while (l < r) {
			ll mid = (l + r + 1) / 2;
			//等差数列的求和公式
			ll x = (i + mid) * (mid - i + 1) / 2;
			if (x <= N) l = mid;
			else r = mid - 1;
		}
		//对结果进行打印输出
		if ((i + r) * (r - i + 1) / 2 == N) {
			sign = 1;
			for (int j = i; j <= r; j++) {
				cout << j << ' ';
			}
			cout << endl;
		}
	}
	if (sign == 0) {
		cout << "NONE";
	}
	return 0;
}

数的范围

#include<iostream>
#include<vector>
using namespace std;
int n, m;
const int N = 100010;
int q[N];
//vector<int> q(n+10);

int main() {
	cin >> n >> m;
	for (int i = 0; i < n; i++) cin >> q[i];
	while (m--)
	{
		int x;
		cin >> x;
		int l=0, r=n-1;
		while (l < r) {
			int mid = l + r >> 1;
			if (q[mid] >= x) r = mid;
			else l = mid + 1;
		}
		if (q[l] != x) cout << "-1 -1" << endl;
		else
		{
			cout << l << ' ';
			int l = 0, r = n - 1;
			while (l < r)
			{
				int mid = l + r + 1 >> 1;
				if (q[mid] <= x) l = mid;
				else r = mid - 1;
			}
			if (q[l] != x) cout << "-1 -1" << endl;
				cout << l << endl;
		}

	}
	return 0;
}

筛素数

分解质因数

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

//分解质因数
int main() {
	int n;
	cin >> n;
	int temp = n;
	for (int i = 2; i < n; i++)
	{
		while (temp%i==0)
		{
			cout << i << ' ';
			temp /= i;
		}
	}
	return 0;
}

筛质数

埃氏筛

定理:一个质数*任何一个不为1的正整数=合数

埃氏筛的基本思想:

假设有一个筛子存放1~N,例如:

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 .........N

先将  2的倍数  筛去:

2 3 5 7 9 11 13 15 17 19 21..........N

再将  3的倍数  筛去:

2 3 5 7 11 13 17 19..........N

 之后,再将5的倍数筛去,再来将7的质数筛去,再来将11的倍数筛去........,如此进行到最后留下的数就都是质数,这就是Eratosthenes筛选方法(Eratosthenes Sieve Method)。

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 1000000;
int prime[1000000];
bool notprime[1000000];
int cnt = 0;

//筛质数(埃氏筛)
int main() {
	int n;
	cin >> n;
	notprime[1] = true;
	for (int i = 2; i <= n; i++)
	{
		if (!notprime[i]) {
			cnt += 1;
			prime[cnt] = i;
		}
		for (int j = i*i; j <= n; j+=i)
		{
			notprime[j] = true;
		}
	}

	for (int i = 1; i <= n; i++)
	{
		cout << prime[i]<<' ';
	}
	return 0;
}
线性筛

线性筛法(又称欧拉筛法)

从小到大枚举每个数

1.如果当前数没划掉,必定是质数,记录该质数

2.枚举已记录的质数 (如果合数已越界则中断)

        (1) 合数未越界,则划掉合数

        (2)条件 i%p==0,保证合数只被最小质因子划掉

                若i是质数,则最多枚举到自身中断

                若i是合数,则最多枚举到自身的最小质数中断

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=100000;
int prime[N];
bool notprime[N];
int cnt;
//线性筛

int main() {
	int n;
	cin >> n;
	notprime[1] = true;
	for (int i = 2; i <= n; i++) {
		if (!notprime[i]) {
			prime[++cnt] = i;
		}
		for (int j = 1; i*prime[j] <= n; j++)
		{
			notprime[i * prime[j]] = true;
			if (i % prime[j] == 0) break;
		}
	}

	for (int i = 1; i < n; i++)
	{
		cout << prime[i] << ' ';
	}
	return 0;
}

做个例题吧

3701. 非素数个数 - AcWing题库

这里涉及到数组越界的问题,需要注意。

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int N = 100000000;
ll prime[N];
bool notprime[N];
int cnt = 0;

int main() {
	ll n = 10000000;
	notprime[1] = true;
	for (ll i = 2; i <= n; i++)
	{
		if (!notprime[i]) {
			prime[++cnt] = i;
		}
		for (ll j = (ll)i * i; j <= n; j += i) {
			notprime[j] = true;
		}
	}
	int a, b;

	while (cin >> a >> b) {
		//for (int i = 0; i <= b; i++) {
		//	cout << prime[i] << ' ';
		//}
		int cnt2 = 0;
		for (int i = 0; i < n; i++)
		{
			if (prime[i] > b)
			{
				break;
			}
			if (prime[i] >= a) {
				cnt2++;
			}
		}
		if (a == 1) cout << b - a - cnt2 << endl;
		else cout << b - a + 1 - cnt2 << endl;
	}
	return 0;
}

DFS

全排列

例题:

给定一个整数 n,将数字 1∼n排成一排,将会有很多种排列方法。

现在,请你按照字典序将所有的排列方法输出。

输入格式

共一行,包含一个整数 n。

输出格式

按字典序输出所有排列方案,每个方案占一行。

数据范围

1≤n≤7

输入样例:

3

输出样例:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

代码:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 10;
int path[N];
bool st[N];
int n;

void dfs(int u) {
	if (u == n) {
		for (int i = 0; i < n; i++)
		{
			cout << path[i] << ' ';
		}
		cout << endl;
	}
	for (int i = 1; i <= n; i++)
	{
		if (!st[i]) {
			path[u] = i;
			st[i] = true;
			dfs(u + 1);
			path[u] = 0;
			st[i] = false;
		}
	}
}

int main() {
	cin >> n;
	dfs(0);
	return 0;
}

N皇后问题

例题:51. N 皇后 - 力扣(LeetCode)

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 10;
char g[N][N];
int col[N], dg[N], udg[N];
int n;

void DFS(int u) {
	if (u == n) {
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < n; j++)
			{
				cout << g[i][j] << ' ';
			}
			cout << endl;
		}
		cout << endl;
	}

	for (int i = 0; i < n; i++)
	{
		if (!col[i] && !dg[u+i] && !udg[n-u+i]) {
			g[u][i] = 'Q';
			col[i] = dg[u + i] = udg[n - u + i] = true;
			DFS(u + 1);
			col[i] = dg[u + i] = udg[n - u + i] = false;
			g[u][i] = '.';

		}
	}

}
int main() {
	cin >> n;
	for (int i = 0; i < N; i++)
		for (int j = 0; j < N; j++)
			g[i][j] = '.';
	DFS(0);

	return 0;
}

递进数字

例题:3710. 递进数字 - AcWing题库

暴力解法(直接枚举),会有部分用例超时。

#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<vector>
using namespace std;

int main() {
	int T;
	cin >> T;
	int l, r;
	while (cin >> l >> r)
	{
		int cnt = 0;
		for (int i = l; i <= r; i++) {
			string s = to_string(i);
			if (s.length() >= 2) {
				int j = 0;
				for (j = 0; j < s.length() - 1; j++)
				{
					if ((s[j + 1] - s[j]) == 1 || (s[j] - s[j + 1]) == 1) {
						continue;
					}
					else
					{
						break;
					}
				}
				if (j >= (s.length() - 1)) cnt += 1;
			}
		}
		cout << cnt << endl;
	}

	return 0;
}

二叉树的遍历

3598. 二叉树遍历 - AcWing题库

#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>
#include<string>
#include<string.h>
using namespace std;
string pre, in;

void dfs(string pre, string in) {
	if (pre == "") return;
	char root = pre[0];
	int k = in.find(root);
	dfs(pre.substr(1,k), in.substr(0,k));
	dfs(pre.substr(k+1), in.substr(k+1));
	cout << root;
}

int main() {
	while (cin >> pre >> in)
	{
		dfs(pre, in);
		cout << endl;
	}
	


	return 0;
}

图的存储

邻接表法(模板)

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
//存储邻接表中的值
int e[N];
//存储邻接表的头
int h[N];
//存储邻接表中的元素的下一个坐标
int ne[N];
//存储当前元素的位置
int index;

//邻接表中添加元素
int add(int a, int b) {
	e[index] = b;
	ne[index] = h[a];
	h[a] = index;
	index++;
}

int main() {
	//初始化所有头部为-1
	memset(h, -1, sizeof h);
	return 0;
}

图的深度优先遍历

模板

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010, M = N * 2;
int n, m;
int e[N], h[N], ne[N];
bool st[N];
int index;


void add(int a, int b) {
	e[index] = b;
	ne[index] = h[a];
	h[a] = index;
	index++;
}
void dfs(int u) {
	//表示被访问过
	st[u] = true;
	for (int i = h[u]; i !=-1 ; i=ne[i])
	{
		int j = e[i];
		if (!st[j]) dfs(j);
	}
}

int main() {
	memset(h, -1, sizeof h);
	dfs(1);

	return 0;
}

树的重心

 例题:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<string>
using namespace std;
const int N = 100010, M = N * 2;
int  h[N], e[M], ne[M];
int st[N];
int n, m;
int index;
int ans = N;

void add(int a, int b) {
	e[index] = b;
	ne[index] = h[a];
	h[a] = index;
	index++;
}
//以u为根节点的子树的点的数量
int dfs(int u) {
	st[u] = true;
	int sum = 1, res = 0;
	for (int i = h[u]; i != -1; i=ne[i])
	{
		int j = e[i];
		if (!st[j]) {
			int s = dfs(j);
			res = max(res, s);
			sum += s;
		}
	}
	res = max(res, n - sum);
	ans = min(res, ans);
	return sum;
}


int main() {
	//初始化头节点
	memset(h, -1, sizeof h);
	cin >> n;
	for (int i = 1; i < n; i++)
	{
		int a, b;
		cin >> a >> b;
		add(a, b);
		add(b, a);
	}
	dfs(1);
	cout << ans << endl;

	return 0;
}

拓扑排序

#include<iostream>
#include<algorithm>
#include<math.h>
const int N = 100010;
using namespace std;
int h[N], e[N], ne[N],idx;
int q[N], d[N];
int n, m;

void add(int a, int b) {
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx;
	idx++;
}

bool topsort() {
	int hh = 0, tt = -1;
	for (int i = 1; i <= n; i++)
	{
		if (d[i] == 0) {
			q[++tt] = i;
		}
	}
	while (hh <= tt) {
		int t = q[hh++];
		for ( int i = h[t]; i !=-1; i = ne[i])
		{
			int j = e[i];
			d[j]--;
			if (d[j] == 0) q[++tt] = j;
		}
	}
	if (tt == n - 1) return true;
	else return false;
}

int main() {
	memset(h, -1, sizeof h);
	cin >> n >> m;
	for (int i = 0; i < m; i++)
	{
		int a, b;
		cin >> a >> b;
		add(a, b);
		d[b]++;
	}
	if (topsort()==true) {
		for (int i = 0; i < n; i++)
		{
			cout << q[i] << ' ';
		}
	}
	else puts("-1");
	return 0;
}

使用STL版本:

#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
const int N = 100010;
int h[N], e[N], ne[N],idx;
int d[N];
queue<int> q;
vector<int> res;
int n, m;
void add(int a, int b) {
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx;
	idx++;
}
bool topsort() {
	for (int i = 1; i < n; i++)
		if (d[i] == 0) q.push(i);
	while (!q.empty())
	{
		int t = q.front();
		res.push_back(t);
		q.pop();
		for (int i = h[t]; i !=-1; i=ne[i])
		{
			int j = e[i];
			d[j]--;
			if (d[j] == 0) q.push(j);
		}
	}
	if (res.size() == n) return true;
	else false;
}


int main() {
	memset(h, -1, sizeof h);
	cin >> n >> m;
	for (int i = 0; i < n; i++)
	{
		int a, b;
		cin >> a >> b;
		add(a, b);
		d[b]++;
	}

	if (topsort()) {
		for (auto i : res) {
			cout << i << ' ';
		}
	}
	else cout << "-1" << endl;

	return 0;
}

基本算法

快速排序

#include<iostream>
#include<vector>
using namespace std;

void quick_sort(vector<int> &q,int l,int r) {
	if (l >= r) return;
	int x = q[l], i = l, j = r ;
	while (i<j) {
		 while (q[i] < x) i++;
		 while (q[j] > x) j--;
		if (i < j) swap(q[i], q[j]);
	}
	quick_sort(q, l, j);
	quick_sort(q, j + 1, r);
}

int main() {
	//待排序序列个数
 	int n;
	cin >> n;
	vector<int> q(n);
	for (int i = 0; i < n; i++) cin >> q[i];
	quick_sort(q ,0 ,n-1);
	for (int i = 0; i < q.size(); i++) cout << q[i]<<' ';

	return 0;
}

拓扑排序

宽搜应用

#include<iostream>
#include<algorithm>
#include<math.h>
#include<queue>
using namespace std;
const int N = 100010;
int h[N], e[N], ne[N], idx;
int q[N], d[N];
int n, m;

void add(int a, int b) {
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx;
	idx++;
}

bool topsort() {
	int hh = 0, tt = -1;
	for (int i = 1; i <= n; i++)
		if (d[i] == 0)
			q[++tt] = i;
	while (hh<=tt)
	{
		int t = q[hh++];
		for (int i = h[t]; i != -1; i=ne[i])
		{
			int j = e[i];
			d[j]--;
			if (d[j] == 0) q[++tt] = j;
		}
	}
	if (tt = n - 1) return true;
	else false;
}


int main() {
	memset(h, -1, sizeof h);
	cin >> n >> m;
	for (int i = 0; i < m; i++)
	{
		int a, b;
		cin >> a >> b;
		add(a, b);
		d[b]++;
	}
	if (topsort()) {
		for (int i = 0; i < n; i++) cout << q[i] << ' ';
	}
	else cout << "-1" << endl;


	return 0;
}

宽搜应用-使用队列库

#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
const int N = 100010;
int h[N], e[N], ne[N],idx;
int d[N];
queue<int> q;
vector<int> res;
int n, m;
void add(int a, int b) {
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx;
	idx++;
}
bool topsort() {
	for (int i = 1; i < n; i++)
		if (d[i] == 0) q.push(i);
	while (!q.empty())
	{
		int t = q.front();
		res.push_back(t);
		q.pop();
		for (int i = h[t]; i !=-1; i=ne[i])
		{
			int j = e[i];
			d[j]--;
			if (d[j] == 0) q.push(j);
		}
	}
	if (res.size() == n) return true;
	else false;
}


int main() {
	memset(h, -1, sizeof h);
	cin >> n >> m;
	for (int i = 0; i < n; i++)
	{
		int a, b;
		cin >> a >> b;
		add(a, b);
		d[b]++;
	}

	if (topsort()) {
		for (auto i : res) {
			cout << i << ' ';
		}
	}
	else cout << "-1" << endl;

	return 0;
}

堆排序

构造堆:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 100010;
int heap[N];
//代表堆的长度
int n;
//表示当前元素的位置
int index;

//构建小顶堆
//从当前结点开始,和它的父节点比较:
//若比父节点小则交换,然后将当前节点下标更新为原父节点下标;
//否则退出
void shift_up(int i) {
	while (i>1 && heap[i]<heap[i>>1]){
		swap(heap[i], heap[i >> 1]);
		i >>= 1;
	}
}


//当前节点与其左右孩子(如果有的话)中较小者作比较:
//若后者比父节点小则交换,并更新当前节点下标为被交换的孩子节点下标;
//否则退出
void shift_down(int i) {
	while ((i << 1)<=index)
	{
		int j = i << 1;//j=2i
		if (j < n && heap[j + 1] < heap[j]) j++;
		if (heap[i] > heap[j])swap(heap[i], heap[j]);
		else break;
		i = j;
	}
}

void push(int x) {
	heap[++index] = x;
	shift_up(index);
}

//弹出堆顶元素
void pop() {
	heap[1] = heap[index--];
	shift_down(1);
}

//返回堆顶元素
int top() {
	return heap[1];
}



int main() {
	memset(heap, 0, sizeof heap);


	return 0;
}

算法步骤为:

  1. 构造初始堆。从最后一个非叶子结点 arr[n/2] 开始,自下而上进行下沉操作;
  2. 将堆顶元素与末尾元素交换,此时的末尾元素从堆中排除,然后再次下沉根节点;
  3. 反复执行步骤 2,直到整个序列有序。

堆排序:

void shift_down(int* arr, int i, int n) {
    while ((i << 1) <= n) {
        int j = i << 1;
        if (j < n && arr[j+1] > arr[j]) j++;
        if (arr[i] < arr[j]) swap(arr[i], arr[j]);
        else break;
        i = j;
    }
}

void heap_sort(int* arr, int n) {
    // init heap
    for (int i = n >> 1; i >= 1; i--)
        shift_down(arr, i, n);
    // shift down from bottom to top
    while (--n) {
        swap(arr[1], arr[n+1]);
        shift_down(arr, 1, n);
    }
} 

前缀和

前缀和的作用是求数组某一区间的和

题目:

#include<iostream>
#include<vector>
using namespace std;

int main() {
	ios::sync_with_stdio(false);
	int n, m;
	cin >> n >>m;
	//输入数组
	vector<int> a(n + 1);
	for (size_t i = 1; i <= n; i++) cin >> a[i];

	//求前缀和数组
	vector<int> s(n + 1);
	for (int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];

	while (m--)
	{
		int l, r;
		cin >> l >> r;
		//求区间和
		int res = s[r] - s[l - 1];
		cout << res << endl;
	}
	return 0;
}

 前缀和(二维)

#include<iostream>
#include<vector>
using namespace std;
int n,m;

int main() {
	ios::sync_with_stdio(false);
	cin >> n >> m;
	vector<vector<int>> a(n + 1);
	vector<vector<int>> s(n + 1);
	for (int i = 0; i <= n; i++) a[i].resize(m + 1);
	for (int i = 0; i <= n; i++) s[i].resize(m + 1);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			cin >> a[i][j];
	//求前缀和数组
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];

	int x1, y1, x2, y2;
	cin >> x1 >> y1 >> x2 >> y2;
	int res = 0;
	//算子矩阵的和
	res = s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1];
	cout << res << endl;
	return 0;
}

前缀和求每个字母的个数

AcWing 3723. 字符串查询 - AcWing

#include<iostream>
#include<algorithm>
#include<map>
#include<cstring>
#include<string>
using namespace std;
const int N = 1000010;
int s[26][N];
char str[N];



int main() {
	ios::sync_with_stdio(false);
	cin >> str+1;
	for (int i = 0; i < 26; i++)
	{
		for (int j = 1; str[j]; j++)
		{
			if (str[j] - 'a' == i) {
				s[i][j] = s[i][j - 1] + 1;
			}
			else
			{
				s[i][j] = s[i][j - 1];
			}
		}
	}
	int Q;
	cin >> Q;
	while (Q--)
	{
		int a, b, c, d;
		cin >> a >> b >> c >> d;

		int same = 1;
		for (int i = 0; i < 26; i++)
		{
			if (s[i][b] - s[i][a-1] != s[i][d] - s[i][c-1]) {
				same = 0;
				break;
			}
		}

		if (same==1) cout << "DA" << endl;
		else cout << "NE" << endl;
	}

	return 0;
}

差分

差分常用来:对于给定的前缀和数组,使得前缀和数组的某一区间里面全部加上某个数。

#include<iostream>
#include<vector>
using namespace std;
int n;

void insert(vector<int> &b,int l, int r, int c) {
	b[l] += c;
	b[r + 1] -= c;
}


int main() {
	cin >> n;
	vector<int> a(n + 2);
	vector<int> b(n + 2);
	for (int i = 1; i <= n; i++) cin >> a[i];

	for (int i = 0; i <= n; i++)
		insert(b, i, i, a[i]);
	for (int i = 1; i <= n; i++) cout << b[i] << ' ';

	int l, r,c;
	cin >> l >> r >> c;
	insert(b, l, r, c);
	for (int i = 1; i <= n; i++)
	{
		b[i] += b[i - 1];
	}
	for (int i = 1; i <= n; i++) cout << b[i] << ' ';

	return 0;
}

3724. 街灯 - AcWing题库

#include<iostream>
#include<math.h>
#include<math.h>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 10010;
int vis[N];
int n, m, k;


int main() {
	while (cin >> n >> m >> k)
	{
		memset(vis, 0, sizeof vis);
		for (int i = 1; i <= n; i++)
		{
			int x;
			cin >> x;
			vis[x - k]++;
			vis[x + k + 1]--;
		}
		//求前缀和
		for (int i = 1; i <= n; i++) vis[i] += vis[i - 1];
		int res = 0;
		int p = 2 * k + 1;
		for (int i = 1; i <= n; i++)
		{
			int len = 0;
			while (vis[i] == 0 && i <= n) {
				len++;
				i++;
			}
			if (len != 0) res += ceil((double)len / p);
		}
		cout << res << endl;
	}
	


	return 0;
}

双指针算法

常用模板:

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

核心思想是将暴力算法优化到O(n)

#include<iostream>
#include<string>
using namespace std;

//问题描述:输入几个单词,用空格隔开,然后输出每个单词,每行一个单词
int main() {
	char ss[1000];
	gets_s(ss);
	int n = strlen(ss);
	for (int i=0; i < n; i++)
	{
		int j = i;
		while (j < n && ss[j] != ' ')j++;
		for (int k = i; k <= j; k++)cout << ss[k];
		cout << endl;
		i = j;
	}

	return 0;
}
最长连续不重复子序列

#include<iostream>
#include<vector>
using namespace std;
int n;


int main() {
	cin >> n;
	vector<int> a(n+2);
	vector<int> s(n+2);
	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 (j < i && s[a[i]]>1) {
			s[a[j]]--;
			j++;
		}
		res = max(res, i - j + 1);
	}
	cout << res << endl;

	return 0;
}

位运算

 题目描述:输入n,k,输出n的二进制表示中第k位(k位是从右往左第k位)。

#include<iostream>
using namespace std;

//方法一
int main() {
	int n,k;
	cin >> n>>k;
	int res = n >> k & 1;
	cout << res << endl;
	return 0;
}

//方法二
int main() {
	int n, k;
	cin >> n >> k;
	int a = n >> k & (-n);
	cout << a <<endl;


	return 0;
}

离散化

将1 ,2 ,6,56,457,2355……这种数组映射到1,2,3,4,5,6……上去。 

(懒得敲了)

全排列

使用STL中的algorithm库

数据结构

单链表

#include<iostream>
#include<vector>
using namespace std;
const int N = 100010;
//val用于存储节点中的值
int val[N];
//Next用于存储当前节点的下一个节点坐标
int Next[N];
//head表示链表的头部,起始为空
int head;
//index用于存储当前访问的节点位置
int index;

//链表初始化
void init() {
	head = -1;
	index = 0;
}

//在头节点后面添加元素
void add_to_head(int x) {
	val[index] = x;
	Next[index] = head;
	head = index;
	index++;

}

//在任意位置的后面添加元素
void add(int k,int x) {
	val[index] = x;
	Next[index] = Next[k];
	Next[k] = index;
	index++;
}

//移除链表中k节点后面的一个节点
void remove(int k) {
	if (k == -1) head = Next[head];
	Next[k] = Next[Next[k]];
}

int main() {
	int m;
	cin >> m;
	init();
	while (m--)
	{
		char op;
		cin >> op;
		int k, x;
		if (op == 'H') {
			cin >> x;
			add_to_head(x);
		}
		else if (op == 'D') {
			cin >> k;
			remove(k-1);
		}
		else if (op == 'I') {
			cin >> k >> x;
			add(k-1, x);
		}
	}
	for (int i = head; i != -1; i = Next[i]) cout << val[i] << ' ';
	cout << endl;
	return 0;
}

双链表

#include<iostream>
#include<vector>
using namespace std;
const int N = 10010;
int e[N], l[N], r[N];
//e数组用来存储节点内存储的元素
//l数组用来存储当前节点中的前一个节点的位置
//r数组用来存储当前节点中的后一个节点的位置
//index用来保存当前节点的位置
int index;
//这里省去头节点和尾节点
//使用0作为头部
//使用1作为尾部

//初始化双链表
void init() {
	r[0] = 1;
	l[1] = 0;
	index = 2;
}

//在双链表中第k个位置后添加一个元素
void add(int k,int x) {
	e[index] = x;
	r[index] = r[k];
	l[index] = k;
	l[r[k]] = index;
	r[k] = index;
}

//删除双链表中第k个位置
void remove(int k) {
	l[r[k]] = l[k];
	r[l[k]] = r[k];
}

int main() {

	return 0;
}

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
//stk数组为模拟栈
int stk[N];
//tt表示当前栈顶指针
int tt;

//插入栈顶元素
void my_push(int x) {
	stk[++tt] = x;
}

//弹出栈顶元素并返回
int my_pop() {
	return stk[tt--];
}

//判断栈是否为空
int is_empty() {
	if (tt > 0) return 1;
	else return 0;
}

int main() {

	return 0;
}

队列

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
//q 数组用来模拟队列
int q[N];
//hh,tt分别代表头指针和尾指针
int hh, tt;

//在队尾插入元素
void my_push(int x) {
	q[++tt] = x;
}

//在队头弹出元素
int  my_pop() {
	return q[hh++];
}

//判断队列是否为空
int is_empty() {
	if (hh <= tt) return 1;
	else return 0;
}

int main() {
	
	return 0;
}

单调栈

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int stk[N];

int main() {
	ios::sync_with_stdio(false);
	int n;
	cin >> n;
	int tt = 0;
	for (int i = 0; i < n; i++)
	{
		int x;
		cin >> x;
		while (tt != 0 && stk[tt] >= x) tt--;
		if (tt) cout << stk[tt] << ' ';
		else cout << -1 << ' ';
		stk[++tt] = x;
	}

	return 0;
}

单调队列

#include<iostream>
#include<algorithm>
#include<stdio.h>
using namespace std;
const int N = 100010;
int q[N], a[N];
int n, k;

int main() {
	scanf_s("%d%d", &n, &k);
	for (int i = 0; i < n; i++) scanf_s("%d", &a[i]);

	int hh = 0, tt = -1;
	for (int i = 0; i < n; i++)
	{
		//判断队头指针是否已经划出滑动窗口
		if (hh <= tt && i - k + 1 > q[hh])hh++;
		while (hh <= tt && a[q[tt]] >= a[i]) tt--;
		q[++tt] = i;
		if (i >= k - 1) cout<<a[q[hh]]<<' ';
	}
	puts("");

	hh = 0; tt = -1;
	for (int i = 0; i < n; i++)
	{
		//判断队头指针是否已经划出滑动窗口
		if (hh <= tt && i - k + 1 > q[hh])hh++;
		while (hh <= tt && a[q[tt]] <= a[i]) tt--;
		q[++tt] = i;
		if (i >= k - 1) cout<< a[q[hh]]<<' ';
	}

	return 0;
}

KMP

 

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int n, m;
char s[N], p[N];
int ne[N];

int main() {
	cin >> n >> p + 1 >> m >> s + 1;
	//求next过程
	for (int i = 2,j = 0; i <= n; i++)
	{
		while (j && p[i] != p[j + 1])j = ne[j];
		if (p[i] == p[j + 1]) j++;
		ne[i] = j;
	}

	//匹配过程
	for (int i = 1,j=0; i <= m; i++)
	{
		while (j && s[i] != p[j + 1])j = ne[j];
		if (s[i] == p[j + 1]) j++;
		if (j == n) {
			//匹配成功
			cout << i - n << ' ';
			j = ne[j];
		}
	}
	return 0;
}

思维题

5025. 安全驾驶 - AcWing题库

#include<iostream>
#include<algorithm>
#include<math.h>
#include<map>
using namespace std;
const int N = 100010;
int d, n;
int K[N], V[N];
map<int,int> mp;


int main() {
	cin >> d;
	cin >> n;
	double t = 0;
	int max_k = 0, min_k = 10000000;
	for (int i = 0; i < n; i++)
	{
		int k, v;
		cin >> k >> v;
		mp[k] = v;
		t = max(t, ((double)d - k) / mp[k]);
	}
	printf("%.6f", (double)(d / t));

	return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值