首页 > 编程语言 >算法笔记(5)

算法笔记(5)

时间:2022-10-05 13:33:49浏览次数:75  
标签:字符 arr int 笔记 算法 str ans size

题目1

一个子序列的消除规则如下:

  1. 在某一个子序列中,如果'1'的左边有'0',那么这两个字符 -> "01"可以消除
  2. 在某一个子序列中,如果'3'的左边有'2',那么这两个字符 -> "23"可以消除
  3. 当这个子序列的某个部分消除之后,认为其他字符会自动贴在一起,可以继续寻找消除的机会

比如,某个子序列"0231",先消除掉"23",那么剩下的字符贴在一起变成"01",继续消除就没有字符了
如果某个子序列通过最优良的方式,可以都消掉,那么这样的子序列叫做“全消子序列”
一个只由'0'、'1'、'2'、'3' 四种字符组成的字符串str,可以生成很多子序列,返回“全消子序列”的最大长度
字符串str长度 <= 200

思路

范围尝试模型。L到R上,返回全消子序列的最大长度。那这个函数就是f(L, R)的形式。两种可能,一种是不考虑L上的字符,一种是必须考虑L上的字符。
先说不考虑:不考虑的话就是返回f(L + 1, R)
考虑:首先看L位置上是否是'1'或者'3',如果是,因为必须考虑那么这种情况下全消子序列的长度就是0。如果L位置上是否是'0'或者'2',那么L+1 ~ R范围上有若干个跟L位置字符配对的字符,假设这些位置是i,每一个这样的字符都可能消掉,所以都调一次递归:f(L + 1, i - 1) + 2(L与i位置可以消掉,所以+2) + f(i + 1, R) ,拿到最大的结果,与第一种不考虑L位置对比,返回最大的。

代码:

int process(string &str, int L, int R)
{
	if (L >= R)
	{
		return 0;
	}
	if (L + 1 == R)
	{
		return (str[L] == '0' && str[R] == '1') || (str[L] == '2' && str[R] == '3') ? 2 : 0;
	}
	int p1 = process(str, L + 1, R);
	if (str[L] == '1' || str[L] == '3')
	{
		return p1;
	}
	char find = str[L] == '0' ? '1' : '3';
	int p2 = 0;
	for (int i = L + 1; i <= R; i++)
	{
		if (str[i] == find)
		{
			p2 = max(p2, process(str, L + 1, i - 1) + 2 + process(str, i + 1, R));
		}
	}
	return max(p1, p2);
}

int main()
{
	string str;
	cin >> str;
	cout << process(str, 0, str.size() - 1) << endl;
	return 0;
}

题目2

给定一个只由0和1组成的字符串S,假设下标从1开始,规定i位置的字符价值V[i]订计算方式如下:

  1. i == 1时,V[i]=1
  2. i > 1时,如果S[i] != S[i-1],V[i] = 1
  3. i > 1时,如果S[i] == S[i-1],V[i] = V[i-1] + 1

你可以随意删除S中的字符,返回整个S的最大价值
字符串长度<=5000

思路

从左到右的尝试模型,当前位置有两种可能性,保留当前位置和删除当前位置。将上一个位置的数字和之前积累的价值传给当前位置。
保留当前位置:当前位置的价值就是,如果上一个位置的数字与当前位置数字一样,则当前数字的价值为V[i-1] + 1,否则就为1
删除当前位置:当前位置的价值就是V[i-1]
两种可能性取最大

代码

int process(string str, int index, char lastNum, int lastValue)
{
	if (index == str.size())
	{
		return 0;
	}
	int curValue = str[index] == lastNum ? lastValue + 1 : 1;
	int p1 = process(str, index + 1, str[index], curValue);
	int p2 = process(str, index + 1, lastNum, lastValue);
	return max(p1, p2);
}

int main()
{
	string str;
	cin >> str;
	cout << process(str, 0, '0', 0);
	return 0;
}

题目3

给定一个字符串str,和一个正数k
返回长度为k的所有子序列中,字典序最大的子序列

思路

单调栈。从左到右依次进单调栈,当前字符的字典序小于等于栈顶字符的字典序才能进栈,否则一直弹出到栈空或能进栈为止。下面就会出现两种可能性:

  1. 遍历完后栈内字符数量大于等于k个,那么从栈底开始往上数k个字符,这k个就是最后的答案。
  2. 遍历到i位置,单调栈的size() + str.size() - i + 1 的结果等于k了,那证明如果再遍历下去可能最后遍历完的时候,单调栈内的字符数量会小于k,此时不再弹出或遍历了,直接将栈内所有字符与包括i位置往后的所有字符(加起来刚好等于k个)返回即可。

代码

int main()
{
	string str;
	int k;
	cin >> str;
	cin >> k;
	vector<char> stack(str.size());
	int size = 0;
	for (int i = 0; i < str.size(); i++)
	{
		while (size > 0 && stack[size - 1] < str[i] && size + str.size() - i> k)
		{
			size--;
		}
		if (size + str.size() - i == k)
		{
			string ans = "";
			for (int j = 0; j < size; j++)
			{
				ans += stack[j];
			}
			ans += str.substr(i, str.size() - i);
			cout << ans << endl;
			return 0;
		}
		stack[size++] = str[i];
	}
	string ans = "";
	for (int i = 0; i < k; i++)
	{
		ans += stack[i];
	}
	cout << ans << endl;
	return 0;
}

题目4

给定一个数组arr,当拿走某个数a的时候,其他所有的数都+a
请返回最终所有数都拿走的最大分数
比如: [2, 3, 1]
当拿走3时,获得3分,数组变成[5, 4]
当拿走5时,获得5分,数组变成[9]
当拿走9时,获得9分,数组变成[ ]
这是最大的拿取方式,返回总分17

思路

从大到小拿即可

代码

int main()
{
	int n;
	cin >> n;
	vector<int> arr(n);
	for (int i = 0; i < n; i++)
	{
		cin >> arr[i];
	}
	sort(arr.begin(), arr.end());
	int ans = 0;
	for (int i = arr.size() - 1; i >= 0; i--)
	{
		ans += (ans * 2) + arr[i];      //总结出的公式
	}
	cout << ans << endl;
	return 0;
}

题目5

把一个01字符串切成多个部分,要求每一部分的0和1比例一样,同时要求尽可能多的划分
比如:01010101
01、01、01、01 这是一种切法,0和1比例为1 : 1
0101、0101 也是一种切法,0和1比例为1 : 1
两种切法都符合要求,但是那么尽可能多的划分为第一种切法,部分数为4
比如:00001111
只有一种切法就是00001111整体作为一块,那么尽可能多的划分,部分数为1
给定一个01字符串str,假设长度为N,要求返回一个长度为N的数组ans
其中ans[i] = str[0...i]这个前缀串,要求每一部分的0和1比例一样,同时要求尽可能多的划分下,部分数是多少
输入:str = "010100001"
输出:ans = [1, 1, 1, 2, 1, 2, 1, 1, 3]

思路

假设当前前缀0和1的比例为a : b,那么它分的的最多的份数的每一份的0和1的比例都为a : b。假设当前前缀是下标为0 ~ j,之前的某个前缀的0和1的比例也为a : b,它的下标是0 ~ i,那么i + 1 ~ j的比例也一定是a : b(数学方法可以证明),那么如果前面有n个前缀拥有a : b的比例,当前前缀最多能分到的份数就是n + 1份,所以就看当前前缀的a : b,在之前的前缀中出现过多少次。可以利用哈希表,每到一个前缀,就储存这个前缀的0 1比。

代码

int gcd(int a, int b)
{
	if (a < b)
	{
		int tmp = a;
		a = b;
		b = tmp;
	}
	if (b == 0)
	{
		return a;
	}
	return gcd(b, a % b);
}

int main()
{
	string str;
	cin >> str;
	int zeros = 0;
	int ones = 0;
	unordered_map<int, unordered_map<int, int>> pre;
	vector<int> ans(str.size());
	for (int i = 0; i < str.size(); i++)
	{
		if (str[i] == '0')
		{
			zeros++;
		}
		else
		{
			ones++;
		}
		if (zeros == 0 || ones == 0)
		{
			ans[i] == i + 1;		//当前前缀全是1或全是0,那么每个字符分成一份就是答案
		}
		else
		{
			int g = gcd(zeros, ones);    //得到a与b的最大公因数
			int a = zeros / g;	//分子
			int b = ones / g;	//分母
			if (pre.count(a) == 0)
			{
				pre[a][0];
			}
			if (pre[a].count(b) == 0)
			{
				pre[a][b] = 1;
			}
			else
			{
				pre[a][b]++;
			}
			ans[i] = pre[a][b];
		}
	}
	for (int i = 0; i < ans.size(); i++)
	{
		cout << ans[i] << endl;
	}
	return 0;
}

题目6

[0, 4, 7]:0表示这里石头没有颜色,如果变红代价是4,如果变蓝代价是7
[1, X, X]:1表示这里石头已经是红,而且不能改颜色,所以后两个数X无意义
[2, X, X]:2表示这里石头已经是蓝,而且不能改颜色,所以后两个数X无意义
颜色只可能是0、1、2,代价一定 >= 0
给你一批这样的小数组,要求最后必须所有石头都有颜色,且红色和蓝色一样多,返回最小代价
如果怎么都无法做到所有石头都有颜色、且红色和蓝色一样多,返回-1

思路

首先如果小数组的数量是奇数个,则返回-1。然后遍历所有数组,找到红色石头、蓝色石头和没有颜色石头的个数,就可以确定没有颜色的石头多少个需要分配给红色,多少个需要分配给蓝色。假设a个石头需要变成红色,b个需要变成蓝色。先把所有无色石头都变成红色(或者蓝色),然后统计代价,这时候看其中哪b个石头变成蓝色减少的代价最多,即数组下标1减去下标2的值最大的b个,把它们变成蓝色再统计新的代价即可。

代码

int main()
{
	int n;
	cin >> n;
	vector<vector<int>> arr(n, vector<int>(3));
	int red = 0;
	int blue = 0;
	int sum = n;
	int ans = 0;
	vector<int> redToBlue(n);
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			cin >> arr[i][j];
			if (j == 0)
			{
				if (arr[i][j] == 1)
				{
					red++;
				}
				else if (arr[i][j] == 2)
				{
					blue++;
				}
			}
			if (j == 1)
			{
				ans += arr[i][1];
			}
			if (j == 2)
			{
				redToBlue[i] = arr[i][1] - arr[i][2];
			}
		}
	}
        if ((sum & 1) != 0 || blue > (sum / 2) || red > (sum / 2))
        {
                cout << -1 << endl;
                return 0;
        }
	sort(redToBlue.begin(), redToBlue.end());
	int needs = sum / 2 - blue;
	for (int i = redToBlue.size() - 1; i >= redToBlue.size() - needs; i--)
	{
		ans -= redToBlue[i];
	}
	cout << ans << endl;
	return 0;
}

标签:字符,arr,int,笔记,算法,str,ans,size
From: https://www.cnblogs.com/hbwang1115/p/16755089.html

相关文章

  • 算法导论(第4章 分治策略)
    目录4.1最大子数组问题暴力求解方法问题变换使用分治策略的求解方法分治算法的分析4.2矩阵乘法的Strassen算法一个简单的分治算法Strassen方法4.3用代入法求解递归式做......
  • Python-错误笔记
    TypeError:sliceindicesmustbeintegersorNoneorhavean__index__method原因1:存在带除法的操作,“/”会生成浮点数,需要将除法符号“/”更改成“//”。原因2:“[......
  • STL学习笔记
    目录STL介绍什么是STL泛性编程STL基本组成STL序列式容器什么是STL容器什么是迭代器什么是序列式容器array容器vector容器deque容器list容器STL关联式容器pair容器map容器mu......
  • 【学习笔记】数据库用户管理和备份
    数据库用户管理和备份 用户管理可视化管理用navicat可视化管理软件进行用户的添加删除和权限的管理新建用户连接用户  sql命令操作对用户的......
  • Python-API笔记
    random.seed()&np.random.seed()np.random.seed()函数用于生成指定随机数。seed()被设置了之后,np,random.random()可以按顺序产生一组固定的数组。如果使用相同的se......
  • 大盘点 | 2020年两篇目标跟踪算法最佳综述
    作者丨cynthiayawain编辑丨极市平台导读 我们对2020年全部计算机视觉综述论文进行了分方向梳理,本文为第四篇,目标跟踪方向。引言在过去的一年中,计算机视觉领域出现了许多优......
  • MYSQL学习笔记之索引
    (一)什么是索引??    索引(Index)是在数据库的字段上添加的,是为了提高查询的效率存在的一种机制。一张表的一个字段可以添加一个索引,当然,多个字段联合起来也可以添加索引。......
  • 基于视觉的数学公式识别算法介绍
    本文为CSIG-DIAR2020学术年会系列报道之一,转载自52cv,CSIG文档图像分析与识别专委会,为中国科技大学大学杜俊老师最新分享。内容较多,建议先收藏再阅读。本文仅做学术分享,如......
  • 图论专题-学习笔记:树上启发式合并(dsu on tree)
    目录1.前言2.详解3.总结1.前言树上启发式合并(dsuontree),是一种类似于启发式合并的方式解决关于部分子树内问题的算法,一般都是什么子树内颜色个数等等的。前置知识:......
  • Dijkstra算法
    求是单源最短路径,即给定一个源点,求其到其他顶点的最短路径。Dijkstra算法就是解决它的,而其前提条件是图中每条边的权重不为负值。朴素版用于处理稠密图,使用邻接矩阵存储......