蓝桥杯练习-完美的代价

蓝桥杯-完美的代价

问题描述
  回文串,是一种特殊的字符串,它从左往右读和从右往左读是一样的。小龙龙认为回文串才是完美的。现在给你一个串,它不一定是回文的,请你计算最少的交换次数使得该串变成一个完美的回文串。
  交换的定义是:交换两个相邻的字符
  例如mamad
  第一次交换 ad : mamda
  第二次交换 md : madma
  第三次交换 ma : madam (回文!完美!)
输入格式
  第一行是一个整数N,表示接下来的字符串的长度(N <= 8000)
  第二行是一个字符串,长度为N.只包含小写字母
输出格式
  如果可能,输出最少的交换次数。
  否则输出Impossible
  样例输入
  5
  mamad
  样例输出
  3

问题分析

	首先,求最值问题大部分是贪心算法的问题,贪心算法的解答一般分为以下几步:
	1.建立数学模型来描述问题
	2.把求解的问题分成若干个子问题
	3.对每个子问题求解,得到子问题的局部最优解
	4.把子问题的解局部最优解合成原来解问题的一个解
	对于本题而言 ,总的问题是求解最少交换次数得到回文字符串。首先观察回文字符串的特点,当字符串长度为
	偶数的时候,字符总是前后对应的。即第一个位置上的元素等于最后一个位置上的元素,第二个元素等于倒数第
	二个位置上的元素...当字符串的长度为奇数的时候,除了中间位置没有对应,其余元素均有对应的元素。

问题求解

经过上述分析,求解总的最少的交换次数可以分解为求解“每一对位置的最少交换次数”,即分解为一个个的子问题
假设一共有n个字符
1.将第一个位置和最后一个位置放置好
2.将第二个位置和倒数第二个位置放置好
3. ...如果是偶数,将中间第n/2位置放好即可,奇数稍微麻烦一点,后面讨论。
那么现在的问题是如何解决放好第一个位置,第二个位置...
	设第一个位置元素为i,可以从后往前遍历,寻找第一个等于i的元素并记录该元素的位置nowIndex,那么
	这个元素应该放在n-1的位置,即目标位置aimIndex,那么这一次需要交换
同样的,接下来就是第二个位置,第三个位置,终止条件就是每放好一个位置就判断是否已经是回文字符串了。

写代码的步骤

我感觉这是最麻烦的事情,如何将一个复杂的事情一步一步地解决,考虑不同情况(BUG),真的让人欲哭无泪。

第一步:得到输入的字符串,判断是否能够变成回文字符串。这个问题可以统计每个字符出现的次数,当只有一个字符出现奇数次的时候是可以变成回文字符串的,如果有两个,那么就无法变成回文字符串了。

private static boolean judgeHuiWen(String string) {
		// TODO Auto-generated method stub
		char[] charS = string.toCharArray();
		int len = charS.length;
		Map<Character,Integer> map = new HashMap<>();
		for(int i = 0;i<len;i++) {
			int cnt = 1;//计算第i个元素重复出现了多少次
			for(int j = 0;j<len;j++) {
				if(i != j && charS[i] == charS[j]) {
					cnt += 1;
				}
			}
			map.put(charS[i], cnt);
		}
		Set<Character> set = map.keySet();
		Iterator<Character> iterator = set.iterator();
		int error = 0;
		while(iterator.hasNext()) {
			if(map.get(iterator.next()) %2 == 1) {
				error += 1;
			}
		}
		if(error>1) {
			return false;
		}
		return true;
	}

我这样写是为了练习一下JAVA中的Map集合,因为Map中得Key是没有重复得元素,对应字符串中的字符,value就是每个字符的出现次数。其实如果在比赛中时间很急的话,可以就写这一点,也是可以拿分的,因为测试用例应该会有一个无法组成回文的例子,可以得到一部分的分数。

第二步:判断字符串是否是回文字符串,如果是那么直接输出cnt,这个cnt是一个静态变量,每一次交换都会使它的值加1,用来输出一共交换了多少次。如果不是,那么开始交换,第一次弄好第一个位置,第二次(如果有的话)弄好第二个位置,依次类推。那么我们就需要一个函数,函数第一个参数就是需要弄的字符串,第二个参数就是这一次弄哪个位置。如下代码:

private static void findHuiWen(String string, int index) {
		// 对第index个位置进行放置
		boolean flag = true;
		if(HuiWen(string) == false) {
				//从后往前遍历
				for(int i = stringLen-1-index;i>index;i--) {
					if(string.charAt(i) == string.charAt(index)) {
						string = moveIndex(string,i,stringLen-index-1);
						flag = false;
						break;
					}
				}
				if(flag == false) {
					findHuiWen(string,index+1);
				}else {
					string = moveIndex(string,index,index+1);
					findHuiWen(string,index);
				}
		}else {
			System.out.println(cnt);
		}
	}

注意,当字符串长度为奇数的时候,情况会有一些不同。比如abdcdca,当弄到字符b的时候,发现只有一个元素,这个时候怎么办呢?一开始我想错了,我以为可以直接将这个元素放中间,就是交换到第3的位置上放着,后面我发现这个是错的,因为现在放在中间,后面弄的时候又会将中间元素弄乱,那么又要重新放中间,做了一些无用功,不是最少的交换次数。正确的做法是,不管!b只出现一次是吧,那就往后边靠靠,让d先来,于是c就和d交换,弄第二个位置,弄好了之后看第三个位置,哟!adbccda,还是没好,那么又弄第三个位置,第三个位置一看,又是孤儿,妈的巴子,又和后面的c换,再弄这个位置。当这个位置弄好后开始弄第四个位置,一看adcbcda,OK,完事,输出cnt。

下面是完整的代码

package com.xl.lanqiao;

import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
public class Main {
	static int stringLen; //输入字符串的长度
	static int cnt = 0;	//统计总共排序次数
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int len = scanner.nextInt();
		stringLen = len;
		String string = scanner.next();
		if(judgeHuiWen(string) == false) {
			System.out.println("Impossible");
		}else {
			findHuiWen(string,0);//开始弄第0个位置
		}
	}

	private static void findHuiWen(String string, int index) {
		// 弄第index个位置
		boolean flag = true;
		if(HuiWen(string) == false) {
				//从后往前遍历
				for(int i = stringLen-1-index;i>index;i--) {
					if(string.charAt(i) == string.charAt(index)) {
						string = moveIndex(string,i,stringLen-index-1);
						flag = false;//说明不是孤儿
						break;
					}
				}
				if(flag == false) {
					findHuiWen(string,index+1);
				}else {//有孤儿的情况
					string = moveIndex(string,index,index+1);
					findHuiWen(string,index);
				}
		}else {
			System.out.println(cnt);
		}
	}

	private static String moveIndex(String string, int nowIndex, int aimIndex) {
		// 交换字符串中的两个位置上的元素
		if(nowIndex == aimIndex) {
			return string;
		}
		char[] charS = string.toCharArray();
		for(int i = nowIndex;i<aimIndex;i++) {
				char temp = charS[i];
				charS[i] = charS[i+1];
				charS[i+1] = temp;
				cnt += 1;
			}
		string = new String(charS);
		return string;
	}

	private static boolean HuiWen(String string) {
		// 判断是否是回文字符串,其实可以用其他方法来判断,我主要是想熟悉一下reverse函数
		//可以开一个for,i从0开始,一个用i,另一个用字符串长度-i
		char[] charS = string.toCharArray();
		List<Character> list = new ArrayList<>();
		for(int i = 0;i<charS.length;i++) {
			list.add(charS[i]);
		}
		Collections.reverse(list);
		for(int i = 0;i<list.size();i++) {
			if(charS[i] != list.get(i)) {
				return false;
			}
		}
		return true;
	}
	private static boolean judgeHuiWen(String string) {
		// 判断能否变成回文字符串
		char[] charS = string.toCharArray();
		int len = charS.length;
		Map<Character,Integer> map = new HashMap<>();
		for(int i = 0;i<len;i++) {
			int cnt = 1;//计算第i个元素重复出现了多少次
			for(int j = 0;j<len;j++) {
				if(i != j && charS[i] == charS[j]) {
					cnt += 1;
				}
			}
			map.put(charS[i], cnt);
		}
		Set<Character> set = map.keySet();
		Iterator<Character> iterator = set.iterator();
		int error = 0;
		while(iterator.hasNext()) {
			if(map.get(iterator.next()) %2 == 1) {
				error += 1;
			}
		}
		if(error>1) {
			return false;
		}
		return true;
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值