牛客编程巅峰赛S1第9场 - 青铜&白银
A.牛牛的字符反转[原题链接]
题意:
求最少对字符串进行几次区间反转操作能实现循环右移 k
位。反转操作指字符串某一区间 [L,R]
内的字符反转,例如 “123456”
,区间 [3,5]
进行反转字符串变为 “125436”
。假设字符串每一位都不同。给定一个字符串长度 n
和循环右移次数 k
,求最少反转次数。
数据范围:
1 <= n,k <= 1e9
思路:
首先应该把 k
对 n
取模,因为循环右移 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恤还蛮好看的(●’◡’●)