题目描述
难度分:1600
输入T(≤104)表示T组数据。所有数据的n之和≤2×105。
每组数据输入n(1≤n≤2×105)和长为n的字符串s,只包含小写字母。
如果对于每个1到n的下标i,都满足s[i]≠s[n+1−i],那么称s为【反回文串】。
每次操作,你可以交换s中的任意两个字符。
最少操作多少次,可以使s是【反回文串】?
输出最小操作次数。如果无法做到,输出−1。
输入样例
10
10
codeforces
3
abc
10
taarrrataa
10
dcbdbdcccc
4
wwww
12
cabbaccabaac
10
aadaaaaddc
14
aacdaaaacadcdc
6
abccba
12
dcbcaebacccd
输出样例
0
-1
1
1
-1
3
-1
2
2
2
算法
贪心+分类讨论
首先可以过滤掉两种无解的情况:
- 字符串s的长度为奇数,根据对反回文串的要求,中心字符一定做不到s[i]≠s[n+1−i]。
- 在字符串s的长度为偶数的情况下,某个字母的出现频率超过了n2,那它一定也会分布在两翼,并且有一翼全部是它,满足不了s[i]≠s[n+1−i]。
剩下的情况全部有解,设counter[k]=(s[i]=s[n+1−i]=k)的i数量(i≤n2)。此时优先对满足s[i]=s[n+1−i]的位置“内部消化”,如果有一对a
和一对b
对称,那么交换一个a
和一个b
就能破坏掉这两对。但是有可能满足s[i]=s[n+1−i]的位置i有奇数个,此时就找一个满足s[i]≠s[n+1−i]的位置交换一下即可破坏掉最后这个落单的对称字母对,因为每个字母的出现次数都不会超过n2。
但是如果某对s[i]=s[n+1−i]=x的字母出现得特别多,超过了所有满足s[i]=s[n+1−i]位置的一半。那就要用其他不同于x的字母来交换,会交换counter[x]次。
综上,最终的答案就是max(maxzc=lettercounter[letter],⌈Σzc=lettercounter[letter]2⌉)(其中⌈.⌉表示对.向上取整)。
复杂度分析
时间复杂度
遍历了两遍字符串s,统计字母频数和满足s[i]=s[n+1−i]的位置数量,因此算法的时间复杂度为O(n)。
空间复杂度
除了输入的字符串,只开辟了长度为26的数组空间,用于统计频数,因此算法的额外空间复杂度为O(Σ),其中Σ表示字符集大小,本题中Σ=26。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <numeric>
using namespace std;
const int N = 200010;
int T, n, counter[26];
char s[N];
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d", &n);
scanf("%s", s + 1);
if(n&1) {
puts("-1");
}else {
int maxfreq = 0;
memset(counter, 0, sizeof(counter));
for(int i = 1; i <= n; i++) {
counter[s[i] - 'a']++;
maxfreq = max(counter[s[i] - 'a'], maxfreq);
}
if(maxfreq > n/2) {
puts("-1");
continue;
}
memset(counter, 0, sizeof(counter));
for(int i = 1; i <= n/2; i++) {
if(s[i] == s[n + 1 - i]) {
counter[s[i] - 'a']++;
}
}
int mx = 0, tot = 0;
for(int i = 0; i < 26; i++) {
mx = max(mx, counter[i]);
tot += counter[i];
}
printf("%d\n", max(tot + 1>>1, mx));
}
}
return 0;
}