记录我的刷题历程
蓝桥杯 basic19试题 基础练习 完美的代价
资源限制
时间限制:1.0s 内存限制:512.0MB
问题描述
回文串,是一种特殊的字符串,它从左往右读和从右往左读是一样的。小龙龙认为回文串才是完美的。现在给你一个串,它不一定是回文的,请你计算最少的交换次数使得该串变成一个完美的回文串。
交换的定义是:交换两个相邻的字符
例如mamad
第一次交换 ad : mamda
第二次交换 md : madma
第三次交换 ma : madam (回文!完美!)
输入格式
第一行是一个整数N,表示接下来的字符串的长度(N <= 8000)
第二行是一个字符串,长度为N.只包含小写字母
输出格式
如果可能,输出最少的交换次数。
否则输出Impossible
样例输入
5
mamad
样例输出
3
思路与大致想法
刚开始的思路是设置两个指针往中间走,两个指针分别寻找与自己最近的那个相同的字母,若是两个指针同时找到了位置距离差距相同的字母,则默认按照从左到右的顺序,将找到的字母相邻置换,直到与当前指针字母构成回文,逐项返回,直到两个指针相遇或者相邻,使循环结束。
遇到的问题
思路不够周全最后导致有很多的遗漏问题,例如回文串的奇数或者偶数数量可能对变换产生的影响,impossible的输出条件,以及是否是离自己最近的变换可以得到最少的变换次数等等。最终得分30
错误代码
import java.util.Scanner;
public class Main {
public static boolean IsHuiWen(String s){
int i=0;
int j=s.length()-1;
while(i<j){
if(s.charAt(i)!=s.charAt(j)){
return false;
}
i++;
j--;
}
return true;
}
public static int feedback1(StringBuilder s1,int i,int j){
int q=0;
for (int p=i+1;p<j;p++){
if(s1.charAt(p)==s1.charAt(i)){
q=p;
break;
}
}
return q-i;
}
public static int feedback2(StringBuilder s1,int i,int j){
int q=j;
for (int p=j-1;p>i;p--){
if(s1.charAt(p)==s1.charAt(j)){
q=p;
break;
}
}
return j-q;
}
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
int n=scanner.nextInt();
String s=scanner.next();
int count=0;
StringBuilder s1=new StringBuilder(s);
if(IsHuiWen(s)){
System.out.println(0);
return;
}
if(n%2==0){
int i=0;
int j=s.length()-1;
while (i<j){
if(s1.charAt(i)!=s1.charAt(j)){
int x=feedback1(s1,i,j);
int y=feedback2(s1,i,j);
if(x>0&&y>0) {
if (x <= y) {
for (int m = x; m < j; m++) {
char temp = s1.charAt(m);
s1.setCharAt(m, s1.charAt(m + 1));
s1.setCharAt(m + 1, temp);
count++;
}
}
else {
for (int m = j-y; m >i; m--) {
char temp = s1.charAt(m);
s1.setCharAt(m, s1.charAt(m - 1));
s1.setCharAt(m - 1, temp);
count++;
}
}
}
else if(x>0){
for (int m = x; m < j; m++) {
char temp = s1.charAt(m);
s1.setCharAt(m, s1.charAt(m + 1));
s1.setCharAt(m + 1, temp);
count++;
}
}
else if (y>0){
for (int m = j-y; m >i; m--) {
char temp = s1.charAt(m);
s1.setCharAt(m, s1.charAt(m - 1));
s1.setCharAt(m - 1, temp);
count++;
}
}
else {
System.out.println("Impossible");
return;
}
}
if(IsHuiWen(s1.toString())){
System.out.println(count);
break;
}
i++;
j--;
}
}
else {
int i=0;
int j=s.length()-1;
while (i<j){
if(s1.charAt(i)!=s1.charAt(j)){
int x=feedback1(s1,i,j);
int y=feedback2(s1,i,j);
if(x>0&&y>0) {
if (x <= y) {
for (int m = i+x; m < j; m++) {
char temp = s1.charAt(m);
s1.setCharAt(m, s1.charAt(m + 1));
s1.setCharAt(m + 1, temp);
count++;
}
}
else {
for (int m = y-i; m > i; m--) {
char temp = s1.charAt(m);
s1.setCharAt(m, s1.charAt(m - 1));
s1.setCharAt(m - 1, temp);
count++;
}
}
}
else if(x>0){
for (int m =i+x; m < j; m++) {
char temp = s1.charAt(m);
s1.setCharAt(m, s1.charAt(m + 1));
s1.setCharAt(m + 1, temp);
count++;
}
}
else if (y>0){
for (int m = j-y; m >i; m--) {
char temp = s1.charAt(m);
s1.setCharAt(m, s1.charAt(m - 1));
s1.setCharAt(m - 1, temp);
count++;
}
}
else {
System.out.println("Impossible");
return;
}
}
if(IsHuiWen(s1.toString())){
System.out.println(count);
break;
}
i++;
j--;
if(j==n/2){
if(s1.charAt(i)==s1.charAt(j)){
System.out.println(count);
break;
}
System.out.println("Impossible");
break;
}
}
}
}
}
结果
由结果可见思路不是很周全
借鉴完别人经验之后的思路
1.先判断impossible的情况
奇数字符串时
abcbe 出现3个奇数次的字符
abcde 出现5个奇数次的字符
则n个字符串如果不是回文,则出现了大于1个的字符出现奇数次的情况
双数字符串时
aaba 出现2个奇数次的字符
acbd 出现4个奇数次的字符
则n个字符如果不是回文,则如果出现1个奇数次字符就不是
2.不是impossible的情况
双数
i到n/2位置时停止循环
设置两个指针往中间走,两个指针分别寻找与自己最近的那个相同的字母,若是两个指针同时找到了位置距离差距相同的字母,则默认按照从左到右的顺序,将找到的字母相邻置换,直到与当前指针字母构成回文,逐项返回,直到两个指针相遇或者相邻,使循环结束。
单数
i,j两个指针相遇时停止循环
先记录出现奇数次的那个字符与中间的距离,然后将它放在一边,
开始双数的排序,最后与双数结果相加输出
正确代码部分
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
int n=scanner.nextInt();
String s=scanner.next();
StringBuilder s1=new StringBuilder(s);
int end=s.length()-1;//最后一个字符
int count=0;//计数
int isApp=0;//出现奇数字符的个数
for (int i=0;i<end;i++){
for (int j=end;j>=i;j--){
if(i==j){//没相同字符
if(n%2==0||isApp==1){
System.out.println("Impossible");
return;
}
isApp=1;
count=count+Math.abs(n/2-i);
}
else if(s1.charAt(i)==s1.charAt(j)){
for (int k=j;k<end;k++){
char temp=s1.charAt(k);
s1.setCharAt(k,s1.charAt(k+1));
s1.setCharAt(k+1,temp);
count++;
}
end--;
break;
}
}
}
System.out.println(count);
}
}
成功截图
思考
我们可以发现用了三层for循环,时间复杂度最高为n^3,感觉最占用时间的是逐项的替换,就思考能否通过只记录交换次数,不进行逐项替换节省时间
优化代码
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
String s = scanner.next();
int result = 0;//计数
int rightNum = 0;//当前位置
boolean[] flag = new boolean[n];
boolean hasOne = false;
for (int i = 0; i < n; i++) {
if (flag[i]) continue;
int temp = 0;
for (int j = n - 1; j >= i; j--) {
if (flag[j]) continue;
if (i == j) {
if ((n % 2 ) == 0 || hasOne) {
System.out.println("Impossible");
return;
}
result += Math.abs(rightNum - (n/2));//加上最后一个奇数插入所需的交换次数
hasOne = true;
} else if (s.charAt(i) == s.charAt(j)) {
rightNum++;//遇到相同字符,就把当前位置指向下一个
result += temp;
flag[j] = true;//说明j位置已被配对过
break;
} else {
temp++;//两个交换的数的距离
}
}
}
System.out.println(result);
}
}
结论
成功把时间复杂度降到了n^2
借鉴来源
https://www.cnblogs.com/cao-lei/p/7163223.html
第一次写博客,思路也许不太清晰,仅作为个人记录学习使用,如果各位大佬们有意见和建议也可以在评论区加以指导。一起进步一起学习