牛客编程巅峰赛S1第9场 - 青铜&白银详解

牛客编程巅峰赛S1第9场 - 青铜&白银

A.牛牛的字符反转[原题链接]

题意:

求最少对字符串进行几次区间反转操作能实现循环右移 k 位。反转操作指字符串某一区间 [L,R] 内的字符反转,例如 “123456” ,区间 [3,5] 进行反转字符串变为 “125436” 。假设字符串每一位都不同。给定一个字符串长度 n 和循环右移次数 k ,求最少反转次数。

数据范围:

1 <= n,k <= 1e9

思路:

首先应该把 kn 取模,因为循环右移 n 次后还是变成原字符串,k = k % n

  • 如果 n == 1 ,那么该字符串不会发生变化,返回 0
  • 如果 k == 0,返回 0
  • 如果 n == 2 && k == 1,假设字符串为 ab,那么循环右移一次的结果为 ba ,只需将原字符串整体反转一次即可,返回 1
  • 如果 k <= 2 || n - k <= 2,也就是说右移或者左移 1-2位。这里只讨论右移 2 位的情况,其他情况类似,首先假设字符串为 "Sab"其中 a, b 为单个字符,S 是一个子字符串。右移 2 位可以得到 abS ,我们可以先反转 Sa 得到 aS'b 其中 S' 表示 S 的反转,然后反转 S'b 得到 abS
  • 否则我们总是可以通过 3 次反转得到循环右移后的字符串。假设原字符串表示为 AB 其中 B 表示长度为 i 子字符串,字符串右移 i 位可以得到 BA。首先整体反转得到 B'A',然后分别对 A'B' 进行反转得到 BA
代码:
int solve(int n, int k) {
    k = k % n;
    if (k == 0 || n == 1) return 0;
    else if (n == 2) return 1;
    else if (k <= 2 || n - k <= 2) return 2;
    else return 3;
}

B.牛牛的木板[原题链接]

题意:

牛牛从牛毕那里拿了一根长度为 n 的白木板,木板被等分成了 n 段(没有被切割,只是虚拟划分成了 n 段),其中有些段被牛毕用颜料染成了黑色。牛牛非常不喜欢黑色,它找来了一桶清洗剂决定对木板进行清洗,但是牛牛发现自己的清洗剂最多只能清洗 m 段。
清洗完后,牛牛会把木板锯成纯色的几段。例如假设木板是 黑黑黑白白白白黑黑黑,就会被锯成 黑黑黑 白白白白 黑黑黑 三段。
牛牛想知道,它足够聪明地清洗木板,能获得的纯白色木板的最大长度是多少。

数据范围:

给定一个长度为 n 的数组 a,a[i] == 1 表示为白色,a[i] == 0 表示为黑色
1 <= n <= 1e6, 1 <= m <= n
0 <= a[i] <= 1

思路:

双指针解决,枚举以 a[i] 为结尾,最多清洗 m 段可得到的连续 1 的最长木板。

代码:
int solve(int n, int m, vector<int>& a) {
	int L = 0, R = 0;
	int ret = 0;
	while (R < n) {
		if (a[R]) ++R;
		else {
		    ++R;
		    --m;
		    ** 如果 m < 0,则需要将左指针右移以增加可清洗木板次数
		    if (m < 0) {
		        while (L < n && a[L]) ++L;
		        ++L;
		        ++m;
		    }
		}
		ret = max(ret, R - L);
	}
	return ret;
}

C.中序序列[原题链接]

题意:

给定一棵有 n 个结点的二叉树的先序遍历与后序遍历序列,求其中序遍历序列。
若某节点只有一个子结点,则此处将其看作左儿子结点

数据范围:

1 <= n <= 1e5

思路:

我们只需要递归找到各个子树的根节点即可,然后按照中序遍历的顺序排列。
前序遍历:根左右;后序遍历:左右根
这里我们用 pi 表示当前节点在 pre 中的下标,si 表示根节点在 suf 中的下标。显然 pre[pi] == suf[si]

  • 如果 pre[pi + 1] == suf[si - 1] ,那么左子节点跟右子节点一样 ,这时代表只有一个子节点,按照题意表示为左子节点
  • 否则就去找到左子节点在 suf 中的下标 i,以及找到右子节点在 pre 中的下标 j
代码:
vector<int> dfs(vector<int>& pre, vector<int>& suf, int pi, int si, int n) {
	if (si < 0 || pi >= n) return {};
	vector<int> ret;
	vector<int> L, R;
	** rval 表示根节点的值
	int rval = suf[si];
	++pi; --si;
	
	int i = si, j = pi;
	while (j < n && suf[si] != pre[j]) ++j;
	while (i >= 0 && suf[i] != pre[pi]) --i;
	
	L = dfs(pre, suf, pi, i, n);
	** 如果左子节点根右子节点不一样说明当前节点右两个子节点
	if (i != si && j != pi)
		R = dfs(pre, suf, j, si, n);
	** 按照中序遍历写入数组
	for (auto & v : L) ret.push_back(v);
	ret.push_back(rval);
	for (auto & v : R) ret.push_back(v);
	return ret;
}

vector<int> solve(int n, vector<int>& pre, vector<int>& suf) {
	return dfs(pre, suf, 0, n - 1, n);
}

战绩:

在这里插入图片描述
看了看排名,果然第一题基本没啥人做出来,这个分类讨论题真是绝了。
另外晚上12:00牛客竟然给了我个surprise,在这里分享一波欧气,感觉这T恤还蛮好看的(●’◡’●)
牛客编程巅峰赛S1第9场 - 青铜&白银详解_第1张图片

你可能感兴趣的:(算法竞赛,算法,字符串,二叉树)