题目描述
难度分:1400
输入T(≤104)表示T组数据。所有数据的n之和≤2×105。
每组数据输入n(1≤n≤2×105)和长为n的字符串s,只包含小写英文字母。
每次操作,选出s中的字典序最大的子序列,将其循环右移一位。例如s=zbca
选出子序列zca
,循环右移一位后变成azc
,此时s变成abzc
。
使s变成非递减,至少要操作多少次?如果无法做到,输出−1。
输入样例
6
5
aaabc
3
acb
3
bac
4
zbca
15
czddeneeeemigec
13
cdefmopqsvxzz
输出样例
0
1
-1
2
6
0
算法
贪心
这种要思考、打草稿的题目一般都能卡我好久。想了很久其实没想到特别好的方案,只能明确第一次要操作的子序列应该是一个以s中最大字母mx起头的最长单调不增序列,假设这个序列的长度为len,那么就要向右移动len−cnt次,其中cnt是序列中mx的出现次数,将整个序列变为单调不减的。不是很确定这里如果右移次数小于len−cnt会不会有什么新情况得到更好的解,或是让无解变有解,但是造了几个小case发现最终都会让右移次数补到len−cnt,少移几次没什么意义。
然后发现一个很重要的事实:再选字典序最大的子序列肯定也会选到mx开头的序列。但经过第一次子序列选择之后,所有mx都应该被排到了最后,这样就没有操作空间了,怎么选都是选出一段mx的后缀。所以只有一次选择子序列的操作,选出来之后把最大的字母右移到尾部。如果此时s有序了那就可以做到,打印移动次数len−1;否则就做不到让s有序,直接打印−1。
接下来关键就在于如何构建第一个最长非增子序列?先遍历s获得一个映射mp“字母→索引列表”,将所有mx的索引加入到一个数组pos中,然后降序遍历比mx小的字母c,只要c存在大于pos.back()的索引(二分查找判断),就把后面所有的c索引加入到pos数组中。通过这种方式得到pos之后,跑一遍相对双指针,把索引pos对应的子序列变为单调不减即可。
复杂度分析
时间复杂度
预处理出mp的时间复杂度为O(nlog2Σ),Σ是字符集大小,本题中Σ=26。然后获得字典序最大的最长非增子序列的索引,在最差情况下要遍历字符集,并且每个字符的索引列表需要做一次二分查找,由于所有字母的索引列表加起来就是O(n)规模的,因此二分查找的时间复杂度为O(log2n)。但是获得的序列可能是O(n)级别的长度,所以这个过程的时间复杂度还是O(n),接下来双指针算法的时间复杂度也是O(n)。
综上,整个算法的时间复杂度为O(nlog2Σ),可以看成是一个常数比较大的线性算法。
空间复杂度
除了输入的字符串,空间消耗就是有序映射表mp,它其中会存储O(n)级别的下标,因此额外空间复杂度为O(n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int t, n;
char s[N];
int main() {
scanf("%d", &t);
while(t--) {
scanf("%d", &n);
scanf("%s", s + 1);
if(is_sorted(s + 1, s + n + 1)) {
puts("0");
}else {
map<char, vector<int>> mp;
for(int i = 1; i <= n; i++) {
mp[s[i]].push_back(i);
}
vector<int> pos;
char mx = mp.rbegin()->first;
for(int x: mp[mx]) {
pos.push_back(x);
}
int mxcnt = pos.size();
mp.erase(mx);
for(char c = mx - 1; c >= 'a'; c--) {
if(mp.count(c)) {
auto& v = mp[c];
int j = upper_bound(v.begin(), v.end(), pos.back()) - v.begin();
int del = v.size() - j;
while(j < v.size()) {
pos.push_back(v[j++]);
}
while(del--) {
v.pop_back();
}
if(v.empty()) mp.erase(c);
}
}
int l = 0, r = pos.size() - 1, ans = pos.size() - mxcnt;
while(l < r) {
swap(s[pos[l]], s[pos[r]]);
l++, r--;
}
printf("%d\n", is_sorted(s + 1, s + n + 1)? ans: -1);
}
}
return 0;
}