[每日一题]回文排列2

Posted1 by 呼延十 on December 12, 2018 Hot:

来源:

lintcode-回文排列2

描述

给定一个字符串s,返回所有回文排列(不重复)。如果没有回文排列,则返回空列表。

样例

给定s = "aabb", 返回 ["abba","baab"].
给定s = "abc", 返回 [].

解题思路:

这道题在我看来就是回文排列全排列的组合题.

首先,对每个出现的字符计数,判断当前字符传可以是回文序列吗?

然后获取回文序列的左半部分(回文序列是对称的,而且如果中间有单个的字符,必然在中间,不用获取),然后对其进行全排列即可.

注意事项

这道题有两个需要注意的地方:

  1. 回文序列可能是形如:ABA这样的,全排列的时候不要带上B.

  2. 全排列的结果可能会多,因为回文里面会有重复值,比如: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

更多学习笔记见个人博客——>呼延十