在 Java 中使用递归的字符串排列

Posted

技术标签:

【中文标题】在 Java 中使用递归的字符串排列【英文标题】:String permutations using recursion in Java 【发布时间】:2015-03-07 12:32:48 【问题描述】:

我遇到了THIS 帖子,它非常努力地解释了打印所有字符串的递归解决方案。

public class Main 
    private static void permutation(String prefix, String str) 
        int n = str.length();
        if (n == 0)
            System.out.println(prefix);
        else 
            for (int i = 0; i < n; i++)
                permutation(prefix + str.charAt(i),
                        str.substring(0, i) + str.substring(i + 1));
        
    

    public static void main(String[] args) 
        permutation("", "ABCD");
    

但是当我们开始从堆栈中弹出时,我仍然无法获得零件。例如,递归一直到permutation("ABCD",""),基本情况相遇并打印ABCD。但是现在会发生什么?我们从函数调用堆栈中弹出permutation("ABC","D")。我们如何处理这些等等?

有人可以帮忙解释一下吗?

另外,我需要一些关于时间复杂度的指针。不像完整的计算,而是一些提示。

【问题讨论】:

【参考方案1】:

更简单的例子:permutation("", "ABC"),将空字符串表示为*

* ABC + A BC + AB C - ABC *
      |      |
      |      ` AC B - ACB *
      |
      + B AC + BA C - BAC *
      |      |
      |      ` BC A - BCA *
      |
      ` C AB + CA B - CAB *
             |
             ` CB A - CBA *

请注意,这看起来像一棵侧放的树。确实,它叫一棵树。当我们启动时,我们会进入("A", "BC")状态;我们继续拨打("AB", "C"),最后拨打("ABC", "")。在这里我们打印我们的输出。然后我们记得我们有未完成的事情,我们回到一个循环,但是上一层的循环只有一个循环。所以我们也完成了,回到("A", "BC") 级别; "BC" 中有两个元素,我们只完成了"B",所以现在轮到"C":我们调用("AC", "B"),然后调用("ACB", "")。现在("A", "BC") 下的所有循环都完成了......但是等等,还有工作要做!因为("", "ABC") 还有两个字母要处理。就这样,一个分支一个分支,就是我们通常所说的“深度优先搜索”。

每个级别都有一个循环。循环收缩(我们在左边的粗分支中迭代 3 次,然后在下一级迭代 2 次,然后只有一个)但仍然有一个循环。所以总的来说,我们做了n * (n - 1) * (n - 2) * ... * 2 * 1 迭代。 O(N!)?

【讨论】:

【参考方案2】:

作为一种递归,你可以使用Stream.reduce方法。首先为每个character-position准备一个可能的字符组合list,然后将这些列表的流连续reduce变成一个列表,通过对列表元素求和。

作为列表元素,可以使用Map&lt;Integer,String&gt;,其中key - 字符在字符串中的位置,value - 字符本身,并将这些地图的内容,不包括那些已经存在的

你会得到一个字符排列列表。

比如你有一个四字串????,那么你要经过三步归约,依次累加结果:

str1 + str2 = sum1 sum1 + str3 = sum2 sum2 + str4 = total
? + ? = ??? + ? = ??? + ? = ??
?? + ? = ????? + ? = ????? + ? = ????? + ? = ????? + ? = ????? + ? = ???
??? + ? = ??????? + ? = ??????? + ? = ??????? + ? = ??????? + ? = ??????? + ? = ????

每个4 字符的15 和为60 和,从而产生4! = 24 排列。

Try it online!

// original string
String str = "????";
// array of characters of the string
int[] codePoints = str.codePoints().toArray();
// contents of the array
System.out.println(Arrays.toString(codePoints));
//[120276, 120277, 120278, 120279]
// list of permutations of characters
List<Map<Integer, String>> permutations = IntStream.range(0, codePoints.length)
        // Stream<List<Map<Integer,String>>>
        .mapToObj(i -> IntStream.range(0, codePoints.length)
                // represent each character as Map<Integer,String>
                .mapToObj(j -> Map.of(j, Character.toString(codePoints[j])))
                // collect a list of maps
                .collect(Collectors.toList()))
        // intermediate output
        //[0=?, 1=?, 2=?, 3=?]
        //[0=?, 1=?, 2=?, 3=?]
        //[0=?, 1=?, 2=?, 3=?]
        //[0=?, 1=?, 2=?, 3=?]
        .peek(System.out::println)
        // reduce a stream of lists to a single list
        .reduce((list1, list2) -> list1.stream()
                // summation of pairs of maps from two lists
                .flatMap(map1 -> list2.stream()
                        // filter out those keys that are already present
                        .filter(map2 -> map2.keySet().stream()
                                .noneMatch(map1::containsKey))
                        // join entries of two maps
                        .map(map2 -> new LinkedHashMap<Integer, String>() 
                            putAll(map1);
                            putAll(map2);
                        ))
                // collect into a single list
                .collect(Collectors.toList()))
        // List<Map<Integer,String>>
        .orElse(List.of(Map.of(0, str)));
// number of permutations
System.out.println(permutations.size()); // 24

// number of rows (factorial of string length - 1)
int rows = IntStream.range(1, codePoints.length)
        .reduce((a, b) -> a * b).orElse(1);

// column-wise output
IntStream.range(0, rows)
        .mapToObj(i -> IntStream.range(0, permutations.size())
                .filter(j -> j % rows == i)
                .mapToObj(permutations::get)
                .map(map -> map.toString().replace(" ", ""))
                .collect(Collectors.joining(" ")))
        .forEach(System.out::println);
//0=?,1=?,2=?,3=? 1=?,0=?,2=?,3=? 2=?,0=?,1=?,3=? 3=?,0=?,1=?,2=?
//0=?,1=?,3=?,2=? 1=?,0=?,3=?,2=? 2=?,0=?,3=?,1=? 3=?,0=?,2=?,1=?
//0=?,2=?,1=?,3=? 1=?,2=?,0=?,3=? 2=?,1=?,0=?,3=? 3=?,1=?,0=?,2=?
//0=?,2=?,3=?,1=? 1=?,2=?,3=?,0=? 2=?,1=?,3=?,0=? 3=?,1=?,2=?,0=?
//0=?,3=?,1=?,2=? 1=?,3=?,0=?,2=? 2=?,3=?,0=?,1=? 3=?,2=?,0=?,1=?
//0=?,3=?,2=?,1=? 1=?,3=?,2=?,0=? 2=?,3=?,1=?,0=? 3=?,2=?,1=?,0=?

另见:How do you check if a word has an anagram that is a palindrome?

【讨论】:

【参考方案3】:

您可以使用mapreduce 方法生成字符串的排列数组。

此解决方案假定原始字符串不包含重复字符,否则应使用 排列映射 Map&lt;Integer,String&gt; 而不是 排列数组 String[]

reduce 方法采用一对排列数组并将它们的元素成对相加,累积结果。比如对一个五字符的字符串?????进行四步归约:

0 [?, ?, ?, ?, ?]
1 [?, ?, ?, ?, ?]
---
sum1: [??, ??, ??, ??, ...]
2 [?, ?, ?, ?, ?]
---
sum2: [???, ???, ???, ...]
3 [?, ?, ?, ?, ?]
---
sum3: [????, ????, ...]
4 [?, ?, ?, ?, ?]
---
total: [?????, ?????, ...]

Try it online!

// original string
String str = "?????";
// array of characters of the string
int[] codePoints = str.codePoints().toArray();
String[] permutations = IntStream.range(0, codePoints.length)
        // intermediate output, character-position
        .peek(i -> System.out.print(i + " "))
        // prepare an array of possible permutations for each character-position
        .mapToObj(i -> Arrays.stream(codePoints).mapToObj(Character::toString)
                // array of characters as strings
                .toArray(String[]::new))
        // intermediate output, array of possible permutations
        .peek(arr -> System.out.println(Arrays.deepToString(arr)))
        // reduce a stream of arrays to a single array
        .reduce((arr1, arr2) -> Arrays.stream(arr1)
                // summation of pairs of strings from two arrays
                .flatMap(str1 -> Arrays.stream(arr2)
                        // filter out those characters that are already present
                        .filter(str2 -> !str1.contains(str2))
                        // concatenate two strings
                        .map(str2 -> str1 + str2))
                // collect into a single array
                .toArray(String[]::new))
        // otherwise an empty array
        .orElse(new String[0]);
// final output
System.out.println("Number of permutations: " + permutations.length);
// number of rows
int rows = permutations.length / 10;
// permutations by columns
IntStream.range(0, rows).forEach(i -> System.out.println(
        IntStream.range(0, permutations.length)
                .filter(j -> j % rows == i)
                .mapToObj(j -> permutations[j])
                .collect(Collectors.joining(" "))));

中间输出,每个字符位置的可能排列数组:

0 [?, ?, ?, ?, ?]
1 [?, ?, ?, ?, ?]
2 [?, ?, ?, ?, ?]
3 [?, ?, ?, ?, ?]
4 [?, ?, ?, ?, ?]

最终输出,按列排列:

Number of permutations: 120
????? ????? ????? ????? ????? ????? ????? ????? ????? ?????
????? ????? ????? ????? ????? ????? ????? ????? ????? ?????
????? ????? ????? ????? ????? ????? ????? ????? ????? ?????
????? ????? ????? ????? ????? ????? ????? ????? ????? ?????
????? ????? ????? ????? ????? ????? ????? ????? ????? ?????
????? ????? ????? ????? ????? ????? ????? ????? ????? ?????
????? ????? ????? ????? ????? ????? ????? ????? ????? ?????
????? ????? ????? ????? ????? ????? ????? ????? ????? ?????
????? ????? ????? ????? ????? ????? ????? ????? ????? ?????
????? ????? ????? ????? ????? ????? ????? ????? ????? ?????
????? ????? ????? ????? ????? ????? ????? ????? ????? ?????
????? ????? ????? ????? ????? ????? ????? ????? ????? ?????

【讨论】:

以上是关于在 Java 中使用递归的字符串排列的主要内容,如果未能解决你的问题,请参考以下文章

全排列 (递归求解+字典序) java 转载

java字符串排列组合查找

java字母和数字排列组合后

java实现全排列问题

php 递归01 利用递归实现按字典顺序全排列

递归全排列