交换的定义是:交换两个相邻的字符
例如mamad
第一次交换 ad : mamda
第二次交换 md : madma
第三次交换 ma : madam (回文!完美!)
第二行是一个字符串,长度为N.只包含小写字母
否则输出Impossible
mamad
题目分析:
求把一个字符串变成回文串的最小交换次数,有难度,需要仔细分析移动过程,分析出算法。
算法分析:
贪心算法。为什么会想到用贪心算法呢,慢慢分析思路,如何交换才能得到回文串并且要保证次数最少。如果先将两端进行对称,再从两端不断向中间推进,再推进的过程不再影响到两端的字符,而是只与当前的状态有关,这样每次都是局部最优,属于贪心算法。必然也是次数最少的方法。
具体如何实现呢,我们从左向右地去遍历数组,然后再从右向左地查找对称的字符。
如果从两端不断向中间推进的过程里,左右两端的指针i,j重合了,说明这是单独的字符,要进行计数,比如本题中d是单独字符。这里分析一下,如果总长为偶数,则必然是Impossible,很好理解,一个偶数串里有一个单独的字符,是不可能成为回文串的。比较难理解的是总长为奇数的情况,如果出现了单独字符,我们可以先跳过它,但是如果出现了第二个单独字符,则Impossible了,奇数串里存在两个单独字符,只交换相邻字符,也不能够得到回文串了。分析得出了两种Impossible的情况之后,我们考虑怎么去处理刚刚跳过的单独字符,其实也不用考虑,移动到最中心去就好了。
上面分析的是左右两端的指针i,j重合的情况。如果两端的指针i,j重合没有重合,就很好办了,说明找到了相同字符,比如本题第一位是m,就从右向左地查找到了第一个m的位置。要做的就是把m移动到最后去。这样我们就把左边第一位和右第一位进行了对称。然后把右端向中间推进一位。
下面循环整个过程就可以,比如把m移动好之后,就从右向左地继续找和左边第二位的a一样的字符,如果找到了,就把它的位置换到倒数第二位,不断对称,也符合最初所说的贪心算法,也就是无后效性和局部最优的两个特性。
算法设计:
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
char[] a = new char[n];
a = sc.next().toCharArray();
sc.close();
int j = n - 1;
int single = 0;
int sum = 0;
int k, i;
for (i = 0; i < j; i++) { //从左向右遍历
k = j;
while (a[i] != a[k]) {
k--; //从右向左查找
}
if (k == i) { //从两端不断向中间推进,左右两端的指针i,j重合
single++; //单独的字母
if (n % 2 == 0 || single > 1) {
//总长为偶数则Impossible
//总长为奇数,先跳过这个单独字母,出现第二个单独字母则Impossible
System.out.println("Impossible");
return;
} else {
sum += n / 2 - i; //单独的字母移动到中心
}
} else { //从两端不断向中间推进,左右两端的指针i,j未重合
for (int m = k; m < j; m++) { //交换到最后一个位置去
a[m] = a[m + 1];
}
sum += j - k;
j--; //右端向中间推进
}
}
System.out.println(sum);
}
}