生成给定字符串的所有排列

461

如何优雅地找出字符串的所有排列组合?例如,对于字符串ba,其排列组合是baab,但是对于更长的字符串,比如abcdefgh,有没有Java实现的例子呢?


3
这里有很多答案:https://dev59.com/43VD5IYBdhLWcg3wXaid。 - Marek Sapota
这是一个非常流行的问题。你可以在这里看一下:http://www.careercup.com/question?id=3861299 - JJunior
9
需要提及一个假设,字符是唯一的。例如,对于一个字符串 "aaaa" 只有一个答案。为了得到更通用的答案,您可以将字符串保存在一个集合中以避免重复。 - Afshin Moazami
1
重复字符被允许吗,还是不允许重复字符?单个字符串是否可以有多个相同字符的出现? - Anderson Green
2
阅读理论(或者如果像我一样懒的话,可以去http://en.wikipedia.org/wiki/Permutation),并实现一个真正的算法。基本上,您可以生成元素排序的序列(它是字符串的事实是无关紧要的),并在顺序中走过,直到回到起点。避免任何涉及递归或字符串操作的内容。 - CurtainDog
显示剩余4条评论
57个回答

0

这可以通过将字符串的每个字母依次插入到之前部分结果的所有位置来迭代地完成。

我们从 [A] 开始,加上 B 变成 [BA, AB],再加上 C,变成 [CBA, BCA, BAC, CAB, 等等]

运行时间为 O(n!),对于测试用例 ABCD 来说,即为 1 x 2 x 3 x 4

在上述乘积中,1 代表 A2 代表 B,以此类推。

Dart 示例:

void main() {

  String insertAt(String a, String b, int index)
  {
    return a.substring(0, index) + b + a.substring(index);
  }

  List<String> Permute(String word) {

    var letters = word.split('');

    var p_list = [ letters.first ];

    for (var c in letters.sublist(1)) {

      var new_list = [ ];

      for (var p in p_list)
        for (int i = 0; i <= p.length; i++)
          new_list.add(insertAt(p, c, i));

      p_list = new_list;
    }

    return p_list;
  }

  print(Permute("ABCD"));

}

0
/*
     * eg: abc =>{a,bc},{b,ac},{c,ab}
     * =>{ca,b},{cb,a}
     * =>cba,cab
     * =>{ba,c},{bc,a}
     * =>bca,bac
     * =>{ab,c},{ac,b}
     * =>acb,abc
     */
    public void nonRecpermute(String prefix, String word)
    {
        String[] currentstr ={prefix,word};
        Stack<String[]> stack = new Stack<String[]>();
        stack.add(currentstr);
        while(!stack.isEmpty())
        {
            currentstr = stack.pop();
            String currentPrefix = currentstr[0];
            String currentWord = currentstr[1];
            if(currentWord.equals(""))
            {
                System.out.println("Word ="+currentPrefix);
            }
            for(int i=0;i<currentWord.length();i++)
            {
                String[] newstr = new String[2];
                newstr[0]=currentPrefix + String.valueOf(currentWord.charAt(i));
                newstr[1] = currentWord.substring(0, i);
                if(i<currentWord.length()-1)
                {
                    newstr[1] = newstr[1]+currentWord.substring(i+1);
                }
                stack.push(newstr);
            }

        }

    }

0

一个通用的实现倒计时Quickperm算法,表示#1(可扩展,非递归)。

/**
 * Generate permutations based on the
 * Countdown <a href="http://quickperm.org/">Quickperm algorithm</>.
 */
public static <T> List<List<T>> generatePermutations(List<T> list) {
    List<T> in = new ArrayList<>(list);
    List<List<T>> out = new ArrayList<>(factorial(list.size()));

    int n = list.size();
    int[] p = new int[n +1];
    for (int i = 0; i < p.length; i ++) {
        p[i] = i;
    }
    int i = 0;
    while (i < n) {
        p[i]--;
        int j = 0;
        if (i % 2 != 0) { // odd?
            j = p[i];
        }
        // swap
        T iTmp = in.get(i);
        in.set(i, in.get(j));
        in.set(j, iTmp);

        i = 1;
        while (p[i] == 0){
            p[i] = i;
            i++;
        }
        out.add(new ArrayList<>(in));
    }
    return out;
}

private static int factorial(int num) {
    int count = num;
    while (num != 1) {
        count *= --num;
    }
    return count;
}

它需要使用列表,因为泛型与数组不兼容。


0

为排列和组合添加更详细的NcK / NcR

public static void combinationNcK(List<String> inputList, String prefix, int chooseCount, List<String> resultList) {
    if (chooseCount == 0)
        resultList.add(prefix);
    else {
        for (int i = 0; i < inputList.size(); i++)
            combinationNcK(inputList.subList(i + 1, inputList.size()), prefix + "," + inputList.get(i), chooseCount - 1, resultList);

        // Finally print once all combinations are done
        if (prefix.equalsIgnoreCase("")) {
            resultList.stream().map(str -> str.substring(1)).forEach(System.out::println);
        }
    }
}

public static void permNcK(List<String> inputList, int chooseCount, List<String> resultList) {
    for (int count = 0; count < inputList.size(); count++) {
        permNcK(inputList, "", chooseCount, resultList);
        resultList = new ArrayList<String>();
        Collections.rotate(inputList, 1);
        System.out.println("-------------------------");
    }

}

public static void permNcK(List<String> inputList, String prefix, int chooseCount, List<String> resultList) {
    if (chooseCount == 0)
        resultList.add(prefix);
    else {
        for (int i = 0; i < inputList.size(); i++)
            combinationNcK(inputList.subList(i + 1, inputList.size()), prefix + "," + inputList.get(i), chooseCount - 1, resultList);

        // Finally print once all combinations are done
        if (prefix.equalsIgnoreCase("")) {
            resultList.stream().map(str -> str.substring(1)).forEach(System.out::println);
        }
    }
}

public static void main(String[] args) {
    List<String> positions = Arrays.asList(new String[] { "1", "2", "3", "4", "5", "6", "7", "8" });
    List<String> resultList = new ArrayList<String>();
    //combinationNcK(positions, "", 3, resultList);

    permNcK(positions, 3, resultList);

}

0

无论如何,在Python中

def perms(in_str, prefix=""):
if not len(in_str) :
    print(prefix)
else:        
    for i in range(0, len(in_str)):
        perms(in_str[:i] + in_str[i + 1:], prefix + in_str[i])

perms('ASD')

你能否给一个新手更好的理解这里正在发生什么? - Xavier Dass

0

我一直在学习递归思想,第一个自然的解决方案是如下所示。一个比较简单的问题是找到一个比当前字符串短一个字母的字符串的排列组合。我假设并且深信不疑,我的函数可以正确地找到比我当前尝试的字符串短一个字母的字符串的排列组合。

给定一个字符串,比如'abc',将其分解为一个子问题,即查找一个字符少的字符串的排列组合,即'bc'。一旦我们有了'bc'的排列组合,我们需要知道如何将其与'a'组合以获得'abc'的排列组合。这就是递归的核心。使用子问题的解来解决当前问题。通过观察,我们可以看到,在'bc'的每个排列组合中插入'a',即'bc'和'cb',将给我们所有'abc'的排列组合。我们必须在相邻字母之间以及每个排列组合的前面和后面插入'a'。例如

对于'bc',我们有

'a'+'bc' = 'abc'

'b'+'a'+'c' = 'bac'

'bc'+'a' = 'bca'

对于'cb',我们有

'a'+'cb' = 'acb'

'c'+'a'+'b' = 'cab'

'cb'+'a' = 'cba'

以下代码片段将阐明此问题。这里是片段的工作链接。

def main():
    result = []
    for permutation in ['bc', 'cb']:
        for i in range(len(permutation) + 1):
            result.append(permutation[:i] + 'a' + permutation[i:])
    return result


if __name__ == '__main__':
    print(main())

完整的递归解决方案如下。这里是完整代码的工作链接。
def permutations(s):
    if len(s) == 1 or len(s) == 0:
        return s
    _permutations = []
    for permutation in permutations(s[1:]):
        for i in range(len(permutation) + 1):
            _permutations.append(permutation[:i] + s[0] + permutation[i:])
    return _permutations


def main(s):
    print(permutations(s))


if __name__ == '__main__':
    main('abc')

0
作为一个Python生成器,具有现代类型提示:
from typing import Iterator


def permutations(string: str, prefix: str = '') -> Iterator[str]:
    if len(string) == 0:
        yield prefix
    for i, character in enumerate(string):
        yield from permutations(string[:i] + string[i + 1:], prefix + character)


for p in permutations('abcd'):
    print(p)

0

基于Mark Byers的回答,我想出了这个解决方案:

JAVA

public class Main {

    public static void main(String[] args) {
        myPerm("ABCD", 0);
    }

    private static void myPerm(String str, int index)
    {
        if (index == str.length()) System.out.println(str);

        for (int i = index; i < str.length(); i++)
        {
            char prefix = str.charAt(i);
            String suffix = str.substring(0,i) + str.substring(i+1);

            myPerm(prefix + suffix, index + 1);
        }
    }
}

C#

我也使用C# 8.0新的范围运算符编写了该函数。

    class Program
    {
        static void Main(string[] args)
        {
            myPerm("ABCD", 0);
        }

        private static void myPerm(string str, int index)
        {
            if (index == str.Length) Console.WriteLine(str);

            for (int i = index; i < str.Length; i++)
            {
                char prefix = str[i];
                string suffix = str[0..i] + str[(i + 1)..];

                myPerm(prefix + suffix, index + 1);
            }
        }
    

我们只需将每个字母放在开头,然后进行排列。
第一次迭代看起来像这样:

/*
myPerm("ABCD",0)  
  prefix = "A"  
  suffix = "BCD"  
  myPerm("ABCD",1)  
    prefix = "B"  
    suffix = "ACD"  
    myPerm("BACD",2)  
      prefix = "C"  
      suffix = "BAD"  
      myPerm("CBAD",3)  
        prefix = "D"  
        suffix = "CBA"  
        myPerm("DCBA",4)  
          Console.WriteLine("DCBA")
*/

0
一个简单的递归C++实现看起来像这样:
#include <iostream>

void generatePermutations(std::string &sequence, int index){
    if(index == sequence.size()){
        std::cout << sequence << "\n";
    } else{
        generatePermutations(sequence, index + 1);
        for(int i = index + 1 ; i < sequence.size() ; ++i){
            std::swap(sequence[index], sequence[i]);
            generatePermutations(sequence, index + 1);
            std::swap(sequence[index], sequence[i]);            
        }
    }
}

int main(int argc, char const *argv[])
{
    std::string str = "abc";
    generatePermutations(str, 0);
    return 0;
}

输出:

abc
acb
bac
bca
cba
cab

更新

如果您想要存储结果,可以将一个vector作为函数调用的第三个参数传递进去。此外,如果您只需要唯一的排列组合,可以使用set

#include <iostream>
#include <vector>
#include <set>

void generatePermutations(std::string &sequence, int index, std::vector <std::string> &v){
    if(index == sequence.size()){
        //std::cout << sequence << "\n";
        v.push_back(sequence);
    } else{
        generatePermutations(sequence, index + 1, v);
        for(int i = index + 1 ; i < sequence.size() ; ++i){
            std::swap(sequence[index], sequence[i]);
            generatePermutations(sequence, index + 1, v);
            std::swap(sequence[index], sequence[i]);            
        }
    }
}

int main(int argc, char const *argv[])
{
    std::string str = "112";
    std::vector <std::string> permutations;
    generatePermutations(str, 0, permutations);
    std::cout << "Number of permutations " << permutations.size() << "\n";
    for(const std::string &s : permutations){
        std::cout << s << "\n";
    }
    std::set <std::string> uniquePermutations(permutations.begin(), permutations.end());
    std::cout << "Number of unique permutations " << uniquePermutations.size() << "\n";
    for(const std::string &s : uniquePermutations){
        std::cout << s << "\n";
    }
    return 0;
}

输出:

Number of permutations 6
112
121
112
121
211
211
Number of unique permutations 3
112
121
211

0

利用 Swift 语言的数组值类型特性的简单解决方案。

func permutation(chrs: [String], arr: [String], result: inout [[String]]) {
   if arr.count == chrs.count {
       result.append(arr)
       return
   }

   for chr in chrs {
       var arr = arr
       if !arr.contains(chr) {
           arr.append(chr)
           permutation(chrs: chrs, arr: arr, result: &result)
       }
   }
}

func test() {
   var result = [[String]]()
   let chrs = ["a", "b", "c", "d"]
   permutation(chrs: chrs, arr: [], result: &result)
}

复杂度为 O(n * n!)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接