来源:
描述
给定一个字符串s,返回所有回文排列(不重复)。如果没有回文排列,则返回空列表。
样例
给定s = "aabb", 返回 ["abba","baab"].
给定s = "abc", 返回 [].
解题思路:
首先,对每个出现的字符计数,判断当前字符传可以是回文序列吗?
然后获取回文序列的左半部分(回文序列是对称的,而且如果中间有单个的字符,必然在中间,不用获取),然后对其进行全排列即可.
注意事项
这道题有两个需要注意的地方:
-
回文序列可能是形如:
ABA
这样的,全排列的时候不要带上B
. -
全排列的结果可能会多,因为回文里面会有重复值,比如:
AAABAAA
.
拿到的进行全排列的字符串为:AAA
,全排列的话会有6种,需要做一个去重的操作.
实现代码
/**
* 回文排列2
* @param s
* @return
*/
public List<String> generatePalindromes(String s) {
//处理空字符串
if (s.equals("")) {
List<String> r = new ArrayList<>();
r.add("");
return r;
}
//获取给定字符串中每个字符的数量
Map<Character, Integer> charNum = canPermutePalindrome(s);
//如果不能形成回文序列,直接返回空列表
if (null == charNum || charNum.keySet().size() == 0
|| charNum.values().stream().filter(per -> per % 2 != 0).count() > 1) {
return new ArrayList<>();
}
//从map中获取要进行全排列的字符串
List<Character> chars = new ArrayList<>();
charNum.entrySet().forEach(entry -> {
int num = entry.getValue();
//考虑到共有5个a的情况,应该拿2个a来进行全排列
while (num > 1) {
chars.add(entry.getKey());
num = num - 2;
}
});
StringBuilder builder = new StringBuilder();
chars.forEach(builder::append);
//全排列的结果并进行去重
Set<String> result = new HashSet<>(quanpailie(builder.toString().toCharArray(), 0));
//获取可能存在可能不存在的中间值
Optional<Character> meduim = charNum.entrySet().stream().filter(per -> per.getValue() % 2 != 0)
.map(Entry::getKey).findFirst();
String c = meduim.map(Object::toString).orElse("");
//获取每一个结果
List<String> r = new ArrayList<>();
result.forEach(per -> {
StringBuilder builder1 = new StringBuilder();
builder1.append(per).append(c);
for (int i = per.length() - 1; i >= 0; i--) {
builder1.append(per.charAt(i));
}
r.add(builder1.toString());
});
return r;
}
/**
* 全排列递归实现
*/
private List<String> quanpailie(char[] cs, int current) {
//结果
List<String> result = new LinkedList<>();
//当前指向数组最后一位时,将数组(全排列的一种)输出到结果集里
if (current == cs.length - 1) {
result.add(new String(cs));
} else {
//循环改变数组的第一个位置的值,并求剩下的其他字符的全排列,并装入结果集.
for (int i = current; i < cs.length; i++) {
//交换当前字符与下一字符
swap(cs, current, i);
//这一块难理解,相当于,在A确定放在第一位的时候,求BC的全排列,并且加上A,形成ABC,ACB放入结果集.
result.addAll(quanpailie(cs, current + 1));
//交换回来,方便下一次交换.
swap(cs, current, i);
}
}
return result;
}
/**
* 交换数组第b,e位置上的值
*/
private void swap(char[] cs, int b, int e) {
char tmp = cs[b];
cs[b] = cs[e];
cs[e] = tmp;
}
public Map<Character, Integer> canPermutePalindrome(String s) {
// 处理空字符串
if (s.length() == 0) {
return null;
}
HashMap<Character, Integer> result = new HashMap<>();
char[] arr = s.toCharArray();
//分别计数每个字符出现的次数
for (char anArr : arr) {
if (null != result.get(anArr)) {
result.put(anArr, result.get(anArr) + 1);
continue;
}
result.put(anArr, 1);
}
//如果奇数个字符的数量大于1个,则返回false,否则返回true.
return result;
}
注意事项
上述代码实现了此题目的功能,但是在性能上存在一些小问题,比如,用递归的方式进行全排列,当字符串过大时,时间复杂度过高,导致性能很差.
暂时没有想到调优的办法,欢迎大佬们帮忙改进.
完。
ChangeLog
2018-12-11 完成以上皆为个人所思所得,如有错误欢迎评论区指正。
欢迎转载,烦请署名并保留原文链接。
联系邮箱:huyanshi2580@gmail.com
更多学习笔记见个人博客------>呼延十