问题描述
回文串,是一种特殊的字符串,它从左往右读和从右往左读是一样的。
小龙龙认为回文串才是完美的。现在给你一个串,它不一定是回文的,
请你计算最少的交换次数使得该串变成一个完美的回文串。
交换的定义是:交换两个相邻的字符
例如mamad
第一次交换 ad : mamda
第二次交换 md : madma
第三次交换 ma : madam (回文!完美!)
输入格式
第一行是一个整数N,表示接下来的字符串的长度(N <= 8000)
第二行是一个字符串,长度为N.只包含小写字母
输出格式
如果可能,输出最少的交换次数。
否则输出Impossible
样例输入
5
mamad
样例输出
3
首先,什么情况下的字符串可以构成回文序列?
1.如果字符串的长度为偶数,那么不能有出现次数为奇数的元素;
2.如果字符串长度为奇数,那么至多只能有一个出现次数为奇数的元素。
解决思路:
首先定义i指向字符串开头,j指向字符串的最后一个元素,进入循环,先让i不动,j从后往前指,寻找与i指向的元素相同的元素。因为j时刻改变,所以需要k来指每次进行配对的字符串的最后一个元素,循环时让j=k即可。
此时有两种情况
1. i != j:说明找到了,此时将j所指元素后移到k所指位置(即与i对称的位置),j与k之间的元素前移为所找到的元素腾出位置;
然后,i指向后移一个,k指向前移一个,再次进行循环,已经配对好的不用管。
2.i = j:说明没有找到,即i所指元素在此时需要进行配对的序列中只出现了一次(但不代表在整个字符串序列中只出现了一次,但在整个序列来说,一定是出现了奇数次)。
此时需要判断字符串长度的奇偶性,若为偶,直接打印"Impossible";若为奇,需要判断是第几次出现这样的情况,若不是第一次出现,直接打印"Impossible"。
如果是第一次出现,这个出现奇数次的元素,一定是要放在中间的,但是现在不能直接将它放在中间(如果放在中间,后面再继续配对,可能会将其再次移走),所以此时只需要跳过这个元素,让i指向下一个元素,而k不变,此时再次配对,与i的元素配对的元素就会放在i对称位置的后一个,此时虽然不对称,但最后只需要将奇数次的元素移到中间,奇数次元素后面到中间位置的元素依次前移,这样就可以对称了。而奇数次元素移到中间位置所需要的次数即为下标之差,即len/2 - 1。此时记录下这个次数就好。
最后跳出循环i与k相等,指向的元素也必然相等,不用移动,所以便可以结束,输出次数。
代码如下:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int len = 0; // 字符串长度
char ch[8005];
scanf("%d", &len);
getchar();
gets(ch);
int i = 0; // 从前面往后指
int j = 0; // 从后面往前指
int k = len - 1; // 由于j每次需要减减值会改变,而j每次开始循环需要指向新的下标,所以用k来记录j需要的起始值
int x = 0;
int sum = 0;
int flag = 0;
for (i = 0; i < k; i++)
{
for (j = k; j >= i; j--)
{
if (i == j) // 说明在此时需要进行操作的字符串中,ch[i]仅出现一次,即没有与之匹配的元素
{
if (len % 2 == 0)
{
printf("Impossible");
return 0;
}
else if (flag == 1)
{
printf("Impossible");
return 0;
}
sum += len / 2 - i; // 将其移到中间所需步数,不能立刻放在中间,最后再进行,但只需要求步数,所以最后也不必真的移动
flag = 1;
break;
}
if (ch[i] == ch[j]) // 找到第一个和i所指元素相等的元素
{
// 此时需要将后面的元素前移
for (x = j; x < k; x++)
{
ch[x] = ch[x + 1];
sum++;
}
ch[k] = ch[i]; // 放在对应位置
k--;
break;
}
}
}
printf("%d", sum);
return 0;
}
这个只记录了次数,如果我们想看看进行回文操作之后的序列,可以在最后进行移动。
代码如下:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int len = 0; // 字符串长度
char ch[8005];
scanf("%d", &len);
getchar();
gets(ch);
int i = 0; // 从前面往后指
int j = 0; // 从后面往前指
int k = len - 1; // 由于j每次需要减减值会改变,而j每次开始循环需要指向新的下标,所以用k来记录j需要的起始值
int x = 0;
int sum = 0;
int flag = 0;
int record = 0;
char temp;
for (i = 0; i <= k; i++)
{
for (j = k; j >= i; j--)
{
if (i == j) // 说明在此时需要进行操作的字符串中,ch[i]仅出现一次,即没有与之匹配的元素
{
if (len % 2 == 0)
{
printf("Impossible");
return 0;
}
else if (flag == 1)
{
printf("Impossible");
return 0;
}
sum += len / 2 - i; // 将其移到中间所需步数,不能立刻放在中间,最后再进行,但只需要求步数,所以最后也不必真的移动
record = i;
flag = 1;
break;
}
if (ch[i] == ch[j]) // 找到第一个和i所指元素相等的元素
{
// 此时需要将后面的元素前移
for (x = j; x < k; x++)
{
ch[x] = ch[x + 1];
sum++;
}
ch[k] = ch[i]; // 放在对应位置
k--;
break;
}
}
}
temp = ch[record];
for(x = record; x < len/2; x++)
{
ch[x] = ch[x+1];
}ch[x] = temp;
printf("%d\n", sum);
printf("%s",ch);
return 0;
}
可以看到此时样例的输出:
写的时候犯的错误:
先进行了判断元素是否相等再判断i == j,这种写法是错误的,如果当i真的等于j的时候,这时应该当作“没有找到”的情况处理,而循环进入,先进行ch[i] == ch[j]的判断,此时i=j,这个条件必定满足,此时就会当作“找到了”处理,此时若k大于j,就会进行交换,就会出错。
若有错误或者不足,还请多多指教,批评指正🥰