【问题标题】:Getting every possible permutation of a string or combination including repeated characters in Java获取字符串或组合的所有可能排列,包括 Java 中的重复字符
【发布时间】:2011-07-04 01:51:54
【问题描述】:

我一直在尝试生成每个可能的 4 个字符串的列表,这些字符串可以由任何给定的字符集组成。我使用了一个函数从一组字符中生成每 4 个字符的组合,但每个字符只使用一次。例如,我需要使用给定字符集的所有可能组合:

String[] elements = {"a", "b", "c", "1", "2", "3"};
int[] indices;
CombinationGenerator x = new CombinationGenerator (elements.length, 4);
StringBuffer combination;
while (x.hasMore ()) {
  combination = new StringBuffer ();
  indices = x.getNext ();
  for (int i = 0; i < indices.length; i++) {
      combination.append (elements[indices[i]]);
  }
  System.out.println (combination.toString ());
}

使用 here 中的 CombinationGenerator 类, 这将返回每个唯一的 4 个字符组合,例如:

'abcd' , 'abc1', 'acb2', 'acb1'

但是,我想要使用给定字符创建的所有可能的字符串。例如:

'aaaa', 'aaab', 'abc1', 'aac1', '11c2'

我已经尝试了我能够找到或想出的所有递归和排列方法,但是除了生成上述所有组合,然后生成每个组合的每个排列之外,我很难获得任何进一步的方法,但我可以不知道如何使用重复字符创建一组组合。

任何帮助,甚至只是关于如何完成的理论都会有所帮助。

【问题讨论】:

    标签: java combinations permutation


    【解决方案1】:

    您必须更具体地说明您希望函数获得什么。 “组合”有很多不同的定义,您还没有指定是需要有序组合还是无序组合。

    从数学上讲,如果你有 n 个元素并且想要一个包含 k 个元素的列表(按重复顺序排列),那么你就可以

    n ^ k
    

    组合。 (在您的原始示例中,6 ^ 4 = 1296 个组合,很多!)。但是,如果您有 n 个元素并且想要其中 k 个元素的 MULTISET(无序重复),那么您就可以

    (n + k - 1)! / (k! * (n - 1)!)
    

    组合,是一个更难的枚举。

    如果 k 很小,您可以使用有限数量的 for 循环生成第一个,但是随着 k 的增长,这会很快变得很麻烦。这强烈暗示需要 RECURSIVE 方法:

    public static String[] getAllLists(String[] elements, int lengthOfList)
    {
        //initialize our returned list with the number of elements calculated above
        String[] allLists = new String[(int)Math.pow(elements.length, lengthOfList)];
    
        //lists of length 1 are just the original elements
        if(lengthOfList == 1) return elements; 
        else
        {
            //the recursion--get all lists of length 3, length 2, all the way up to 1
            String[] allSublists = getAllLists(elements, lengthOfList - 1);
    
            //append the sublists to each element
            int arrayIndex = 0;
    
            for(int i = 0; i < elements.length; i++)
            {
                for(int j = 0; j < allSublists.length; j++)
                {
                    //add the newly appended combination to the list
                    allLists[arrayIndex] = elements[i] + allSublists[j];
                    arrayIndex++;
                }
            }
    
            return allLists;
        }
    }
    

    此方法不仅会生成所有列表,还会按顺序枚举它们。也就是说,输出将是

    aaaa
    aaab
    aaac
    aaa1
    aaa2
    aaa3
    aaba
    aabb
    aabc
    aab1
    ...
    3323
    333a
    333b
    333c
    3331
    3332
    3333
    

    使用您的原始输入。它还可以生成任意长度的单词(对此要非常小心!仅使用长度为 8 的单词我就得到了 1,679,616 个组合!)。

    如果该方法让您感到困惑(这是一种递归方法,因此有点难以理解),或者如果您想解决第二个组合问题,请随时提问。此外,此方法效率较低,因为它会重新计算所有子列表的组合,因此对于非常长的列表不可行。如果你真的想要效率,你可以将已经计算好的元组存储在一个全局列表中。

    【讨论】:

    • 非常感谢!这个解决方案解决了我的问题!我理解递归的想法,但我仍在努力解决解决方案:(虽然这太棒了,而且给了我很多思考,我很想寻求一些帮助来理解递归以及它是如何实现的有效,我现在要尝试理解它,但我想我很快就会回来哈哈!
    【解决方案2】:

    如果你想在 Python 中使用它,你不需要知道如何编程!

    import itertools
    for p in itertools.permutations('abc123', 4):
        print ''.join(p)
    

    【讨论】:

    • 首先,问题是关于java的。第二,开线程者想知道如何编程。并且总是更好地知道,如何做事而不是盲目地使用它们......
    • 只标记为问题是关于 java 的,而不是关于 python 的。在这种情况下,python 不可用。
    【解决方案3】:

    递归解决方案似乎也很简单:

    public class TestCombinations {
    
    public static void main(String[] args) {
        String[] elements = {"a", "b", "c", "1", "2", "3"};
        int maxLength = 4;
        combineStringFromElements(elements, "", maxLength);
    }
    
    private static void combineStringFromElements(String[] elements, String currentString, int maxLength) {
        if (currentString.length() == maxLength) {
            System.out.println(currentString);
            return;
        }
        for (String element : elements) {
            combineStringFromElements(elements, currentString + element, maxLength);
        }
    }
    
    }
    

    代码既不干净也不高效,只是为了演示逻辑。

    【讨论】:

    • 也是一个很好的答案,谢谢,我需要坐下来思考递归是如何工作的,并尝试将我的头绕在它周围以正确理解逻辑,为我思考了很多!谢谢大家:)
    • 非常感谢!这很棒,因为我可以直接将其写入文件,而不是将它们全部加载到内存中并导致系统死机。
    【解决方案4】:

    您可以将元素视为数字。想想我们如何通过计数得到“0”-“9”的所有可能组合。从 0000、0001、0002、...、0010、0011 等开始。使用相同的过程,就好像您有一个以 6 为基数的数字系统(或以 n 为基数,其中 n 是您的 elements 数组的长度.

    aaaa, aaab, aaac, ...,
    aaba, aabb, aabc, aab1, aab2, aab3, .....,
    aa32, aa33, abaa, etc.
    

    交替显示最后一位数字的每个组合,然后前进前一位数字并重复。当倒数第二个数字通过每个元素时,然后推进倒数第三个数字,依此类推。当你到达“3333”时,你就完成了。

    你的代码应该是这样的:

    string comb;
    for (int i = 0; i < elements.length; i++)
        for (int j = 0; j < elements.length; j++)
            for (int k = 0; k < elements.length; k++)
                for (int m = 0; m < elements.length; m++) {
                    comb = elements[i] + elements[j] + elements[k] + elements[m];
                    // do something with the combination
                }
    

    还有其他更有效的方法来完成相同的事情,例如存储中间 1、2、3 字符的字符串。还有递归解决方案。但这是大体思路,对于您现在使用的数据大小应该足够快(您总共有 6^4 = 1296 个组合。

    【讨论】:

      【解决方案5】:

      这里有一些 python 代码可以做你想做的事情:

      answer = []
      def permute(chars, s=''):
          if len(s) == 4:
              answer.append(s)
          else:
              for char in chars:
                  permute(chars, s+char)
      

      希望对你有帮助

      这种类型的算法称为recursive descent,以防您还没有检查过。

      【讨论】:

      • 我建议你也提一下,如果itertools.combinations 不可用,这确实是需要的。
      • @ΤZΩΤZΙΟΥ:是的。我只是想展示它是如何在引擎盖下工作的。但是你是对的,如果有一个库可以做你想做的事,那么重新发明轮子很少是答案。
      猜你喜欢
      • 1970-01-01
      • 2011-05-26
      • 1970-01-01
      • 1970-01-01
      • 2017-11-11
      • 2015-09-18
      • 2021-06-07
      • 1970-01-01
      • 2019-08-06
      相关资源
      最近更新 更多