本代码只是一道正确测试率为60%的题目,此文只为作为本人笔记留底,思路正确但未成功完成,代码不值得参考。
/**
* 问题描述
回文串,是一种特殊的字符串,它从左往右读和从右往左读是一样的。小龙龙认为回文串才是完美的。现在给你一个串,它不
一定是回文的,请你计算最少的交换次数使得该串变成一个完美的回文串。
交换的定义是:交换两个相邻的字符
例如mamad
第一次交换 ad : mamda
第二次交换 md : madma
第三次交换 ma : madam (回文!完美!)
输入格式
第一行是一个整数N,表示接下来的字符串的长度(N <= 8000)
第二行是一个字符串,长度为N.只包含小写字母
输出格式
如果可能,输出最少的交换次数。
否则输出Impossible
样例输入
5
mamad
样例输出
3
*
* 思路:要有一个判断回文的函数,一个用来交换回文的函数,一个用来在剩余串中寻找相同元素的函数(应该从字符串最后往
* 前寻找),一个判断字符个数为单数的函数。
*
* 交换回文的函数:从字符串第一位开始,逐位读取,比如读第一位,若有相同的则将该字符放至最后一位,下次循环
* 就不用管最后一位了,因为那是对称好的了。
* 关键在于:如果第一个字符就为出现次数为奇数的字符,那么将它交换到中间位置,接下来交换其他字符时,每次的
* 交换次数都会多一次。这其实是一种普遍的规律。
* Impossible 的两种情况:
n为奇数时,如果已经有一个字符出现的次数为奇数,还找到了一个字符出现的次数为奇数,那么就不能构成回文串;
n为偶数时,只要找到有一个字符出现的次数为奇数,那么就不能构成回文串。
*
*/
import java.util.*;
public class BASIC_19_8_31 {
//用于标记单数位置,以便于判断回文
static int mid_location = -1;
static int ad_tag = 0;
//判断是否回文的函数.√
public static boolean AdjustSymmetric(char str[], int N){
boolean ad = false;
for(int i = 0;i <= N/2;i++){
if(ad_tag == 0){
if(str[i] == str[N-i-1]){
ad = true;
continue;
}else{
ad = false;
break;
}
}else if(ad_tag == 1){
if(i < mid_location){
if(str[i] == str[N-i-1]){
ad = true;
continue;
}else{
ad = false;
break;
}}else if(i == mid_location){
}else if(i > mid_location){
if(str[i] == str[N-i]){
ad = true;
continue;
}else{
ad = false;
break;
}
}
}
}
return ad;
}
//根据下标对字符串内的元素进行回文交换的函数,逻辑是从下标开始位逐一向后交换直至结束位,因为题目要求“交换”
//的含义为:交换两个相邻的字符;并返回交换次数.。该函数必须start_index比end_index小。
//该函数功能是将字符串下标第start_index位数值移到下标为end_index位置上,并且进行机制是相邻两位逐一交换。
//如输入“5,mamda”,运行Swap(str2,2,4)得到结果madam。输入“6,adcdef”,运行Swap(str2,2,4)得到结果abdecf。
//√.
public static int Swap(char str[], int start_index,int end_index){
int times = 0;
for(int i = start_index;i < end_index;i++){
char tmp = str[i + 1];
str[i + 1] = str[i];
str[i] = tmp;
times ++;
//System.out.println("swapping : i : " + i + " ,str situation is : " + String.valueOf(str));
}
return times;
}
//进行回文交换的函数.
public static void ToSymmetrical(char str[],int N){
char ch1;//当前读取元素的值
int tag = 0;//标记剩余值中有无相等的值
int mid_tag = 0;//若读取到个数为单的元素则赋值1,此后继续循环对称,最后结束再把单数的值换到中间去
int mid_start = 0;
int mid_end= 0;
boolean ad = false;//标记是否为回文
int times = 0;//进行交换的次数
int start_index = 0;//获取已找到的元素的位置
int end_index = 0;//获取对称位置也就是需要交换过去的位置
for(int i=0;i < N;i++){
ch1 = str[i];//获取到需要回文对称的元素
//以下是寻找函数
for(int j=N-i-1;j > i;j--){
if(ch1 == str[j]){
tag = 1;
start_index = j;
if(mid_tag == 0){
end_index = N - i - 1;
}else if(mid_tag == 1){
end_index = N - i;
}
break;
}else{
tag = 0;//默认要两数不相等时应该要将标记设回0
}
}
if(tag == 1){
if(start_index == end_index){
}else{
//逻辑上start_index小于end_index,因为start_index是从end_index下标往前移的结果
int time = Swap(str,start_index,end_index);//进行交换并获得交换次数
times = times + time;
ad = AdjustSymmetric(str,N);
if(ad == true){
Swap(str, mid_start, mid_end);
break;
}
}
}else{
//System.out.println("Impossible");//在此将单数移到中间去,并继续循环
start_index = i;
mid_location = i;
ad_tag = 1;
mid_start = i;
end_index = N/2;//此处正是处理单数的情况。
mid_end = N/2;
int time = end_index - start_index - 1;//不交换只获得交换次数
//i--;
mid_tag = 1;
times = times + time;
ad = AdjustSymmetric(str,N);
if(ad == true){
break;
}//——2019.9.1状态不好,明天再测试,先把代码敲下。
}
}
if(ad == true){
System.out.println(times);
}else{
System.out.println("Impossible");
}
}
//个数为单数的字符应放在中间,否则可能会出现读到最后一个该字符时没找到匹对的对象而判断为不可能。这是欠考虑的
//地方,正在想方案。——19.8.31
//可以当找到数量仅有一个的字符(简称:单数字符)时,不直接判断跳出,而是将它放右半边最左边一个,也就是中间
//位置,并继续判断,这样的话最后循环结束还得判断一次是否对称。若是出现两个单数字符则直接Impossible.——19.9.1
public static void main(String args[]){
int N;
String str = null;
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
str = sc.next();
char str2[] = str.toCharArray();
if(AdjustSymmetric(str2,N) == false){
ToSymmetrical(str2,N);
}else{
//表示字符串已回文,不用交换。
System.out.println("0");
}
//Swap Tester.
//System.out.println("The swap return result is : " + Swap(str2,2,4) + " swap result is : "
// + String.valueOf(str2));
//Symmetric Tester.
//System.out.print("Array is : " + str + ", symmetric is : " + AdjustSymmetric(str2,N));
}
}
————————————————分界线——————————————————
大概两天后…
在其他博文上参考(偷看)了其他大神的代码,相比较的感想就是,对于大神,对Java语法的深入掌握后,能够灵活使用多种方法,再加上对数值运算的熟悉以至于可以准确对数值的描述。
现在看来,自己完全是把Java当成C写了…
根据题目我们都知道,Impossible 的两种情况:
n为奇数时,如果已经有一个字符出现的次数为奇数,还找到了一个字符出现的次数为奇数,那么就不能构成回文串;
n为偶数时,只要找到有一个字符出现的次数为奇数,那么就不能构成回文串。
则其实可以省去判断回文的函数AdjustSymmetric,因为只要在以上两种情况之外的字符串,是一定可以得到回文的,就不用重复在其他函数里判断且浪费内存空间。
其次,并不需要函数Swap,其实交换次数直接用两下标相减再减一即可得到的,写太多函数不仅增大代冗余而且增大内存开销。
再来就是,对于需要重复判断的情况,除了用循环,可以学会考虑用递归,因为递归好实现且代码更直观易维护(不过相对于循环,递归也有一些缺点,比较递归是重复调用函数,递归使用栈机制(C++)实现,每深入一层就要占用一块栈数据区域)。
最后就是,多掌握Java的api以及更多方便的方法,能使代码更简洁且更好实现,更好解决问题。当然这少不了多积累,以及多学习和多练习,比如此次学到的StringBuilder。
import java.util.*;
public class BASIC_19_9_7 {
public static boolean Adjuster(String str){
int nums[] = new int[26];
boolean ad = true;
int odds = 0;
//用数组nums的下标存储字符串中字母的个数
for(int i=0;i < str.length();i++){
nums[str.charAt(i) - 'a']++;
}
//统计字母数量为单数的个数
for(int i=0;i < 26;i++){
if(nums[i] % 2 != 0){
odds++;
}
}
if(odds > 1){
ad = false;
}
return ad;
}
//使用递归进行配对
public static int FakeSymmetrical(String str){
if(str.length() == 1 || str.length() == 2){
return 0;
}
int distance = str.lastIndexOf(str.charAt(0));//获得当前字符charAt(0)的最后一次出现处的索引
if(distance == 0){
//表明此处字符就是个数为单数的字符
return str.length()/2 + FakeSymmetrical(str.substring(1,str.length()));
}else{
//表明此处是成双出现的字符,处理方法为获取两下标差值后再去除此两下标处的值,最后获取的只有交换次数
//并没有得到交换好的字符串
StringBuilder strB = new StringBuilder(str);
strB.deleteCharAt(distance);
strB.deleteCharAt(0);
return str.length() - distance - 1 + FakeSymmetrical(strB.toString());
//str.length() - distance - 1是distance上的字符移到字符串最后一位的交换次数,以实现与第一位对称
//必须得先删除distance上的值再删0上的值,否则先去掉0的话后面的值会前移。
}
}
public static void main(String args[]){
int N = 0;
String str = null;
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
str = sc.next();
if(Adjuster(str)){
System.out.println(FakeSymmetrical(str));
}else{
System.out.println("Impossible");
}
sc.close();
}
}
ps:半年前喜欢上玩300英雄(一类moba游戏类似于英雄联盟),刚开始玩一直白给,但因为喜爱所以一直有玩。在昨晚的一局,两边都有萌新,自己终于成为队伍中的高级玩家时,终于成功carry了队友!所以,我相信,坚持下来的种子,总会有开花的一天!