通过从数组中选择来创建排列

时间:2015-12-23 00:50:25

标签: java arrays recursion permutation

我有一个2D数组,可以存储与您在电话键盘上看到的字母相对应的不同字母。

char [][] convert = 
    {   {},
        {'A','B','C'},
        {'D','E','F'},      
        {'G','H','I'},
        {'J','K','L'},
        {'M','N','O'},
        {'P','R','S'},
        {'T','U','V'},
        {'W','X','Y'}
    };

如果我想找到5个字母单词的所有可能排列,每个单词从2D数组的前5行各取1个字母,我该怎么做?我正在考虑递归,但这让我很困惑。

为了使这个问题更容易理解,以下是一个例子:

3个字母的单词取自第1行的第一个字母{'A','B','C'},第3行的第二个字母{'G','H','I'},以及第6行的第3个字母{'P','R','S'}。共有27种可能的结果:AGP AGR AGS AHP AHR AHS AIP AIR AIS BGP BGR BGS BHP BHR BHS BIP BIR BIS CGP CGR CGS CHP CHR CHS CIP CIR CIS

5 个答案:

答案 0 :(得分:4)

首先要注意的是,如果你通过从5行中的每一行中选择3个字符中的一个来制作单词,那么总共会有3个 5 = 243个单词。无论您如何实施该计划,它最终都必须构建这243个单词。

递归是一个很好的实现策略,因为它清楚地表明你正在选择第一行中的三个字符之一,并且对于每个选择,你继续选择第二行中的三个字符之一,等等。

在下面的Java程序中,makeWord的第一个版本是一个递归函数,它选择由currentRowIndex索引的行中的字符,并将该字符附加到wordBuffer。如果这是最后一行,则单词已完成,并将其附加到单词列表中。否则,该函数调用自身在行currentRowIndex + 1上工作。

请注意,wordBuffer的当前状态会继续进行递归调用。只有在从递归调用返回后,我们才会删除wordBuffer中的最后一个字符。

makeWord的第二个版本允许您传递一系列行索引,这些行索引指定要从中选择字符的行。例如,要从第1,3和6行中选择字符,请调用:

permuter.makeWord(new int[]{ 1, 3, 6 }, 0);

您可以使用main方法而不是当前行代替该调用,这会导致使用第1行到第5行中的字符构建单词:

permuter.makeWord(1, 5);

如果仔细查看makeWord方法,你会发现第一个方法在字符串完成时没有递归,而第二个方法只递归一次然后提前返回,因为{{1 }}。后一种方法效率稍差,因为它需要再进行一次递归调用,但您可能会发现它更清楚地表达了递归的概念。这是一个品味问题。

position == indices.length

答案 1 :(得分:4)

试试这个,如果你可以使用Java8

jasmine

您也可以编写递归版本。

static Stream<String> stream(char[] chars) {
    return new String(chars).chars().mapToObj(c -> Character.toString((char)c));
}

public static void main(String[] args) {
    char [][] convert = 
        {   {},
            {'A','B','C'},
            {'D','E','F'},      
            {'G','H','I'},
            {'J','K','L'},
            {'M','N','O'},
            {'P','R','S'},
            {'T','U','V'},
            {'W','X','Y'}
        };
    stream(convert[1])
        .flatMap(s -> stream(convert[2]).map(t -> s + t))
        .flatMap(s -> stream(convert[3]).map(t -> s + t))
        .flatMap(s -> stream(convert[4]).map(t -> s + t))
        .flatMap(s -> stream(convert[5]).map(t -> s + t))
        .forEach(x -> System.out.println(x));
}

并称之为。

static Stream<String> permutation(char[][] convert, int row) {
    return row == 1
        ? stream(convert[1])
        : permutation(convert, row - 1)
            .flatMap(s -> stream(convert[row]).map(t -> s + t));
}

答案 2 :(得分:2)

这可以使用动态编程方法解决。

取前两行并组合数组中的所有字符串。这将给你一个大小为m * n的结果数组。其中m是第一个数组的大小,n是第二个数组的大小。在你的情况下,它是9.然后将结果数组分配给第一个数组,并将第三个数组分配给第二个数组。重复它直到第五个数组。这将为您提供前五个阵列中所有可能的字符串。

public static String[] getAllCombinations(String array[][], int l) {
    String a[] = array[0];

    String temp[]=null;
    for(int i=1;i<l;i++) {
        int z=0;
        String b[] = array[i];
        temp = new String[a.length*b.length];
        for(String x:a) {
            for(String y:b) {
                temp[z] = ""+x+y;
                z++;
            }
        }
        a = temp;
    }
    System.out.println(temp.length);
    return temp;
}

这个功能应该这样做。

答案 3 :(得分:1)

这是一种可能的解决方案。

首先定义要按下的键序列。例如,如果要从数组的前五行各取一个字母,则序列将为(1,2,3,4,5)(因为第一行为空)。如果你想拼写“叠加”,那么序列将是(6,7,1,1,4)。

然后循环完成序列。在每个步骤中,您将获得与序列的此位置对应的字符数组。然后生成到目前为止所有组合产生的所有单词,这些单词是上一步中的所有单词以及当前步骤的所有字符。

最后,最后一步的结果是包含所有可能单词的最终结果。

char [][] convert =  {   
  {},             // 0
  {'A','B','C'},  // 1
  {'D','E','F'},  // 2
  {'G','H','I'},  // 3
  {'J','K','L'},  // 4
  {'M','N','O'},  // 5
  {'P','R','S'},  // 6
  {'T','U','V'},  // 7
  {'W','X','Y'}   // 8
};

// Sequence of keys to be pressed. In this case the pressed keys are
// [ABC], [DEF], [GHI]
int[] sequence = new int[] {1, 2, 3};

// List for storing the results of each level.
List<List<String>> results = new ArrayList<>();

for(int i=0; i<sequence.length; i++){

  // Results of this level
  List<String> words = new ArrayList<>();
  results.add(words);

  List<String> prevLevelWords;
  if(i==0){
    prevLevelWords = Collections.singletonList("");
  } else {
    prevLevelWords = results.get(i-1);
  }

  char[] thisLevelChars = convert[sequence[i]];

  if(thisLevelChars.length == 0){
    words.addAll(prevLevelWords);
  } else {
    for(String word : prevLevelWords){
      for(char ch : convert[sequence[i]]){
        words.add(word + ch);
      }
    }
  }
}

List<String> finalResult = results.get(sequence.length-1);

for(String word : finalResult) {
  System.out.println(word);
}

Run it

答案 4 :(得分:1)

这是使用Java 8流的备用解决方案。

public class Combiner {
    private List<String> combos = new ArrayList<>();

    public Stream<String> stream() {
        return combos.stream();
    }

    public void accept(char[] values) {
        if (combos.isEmpty()) {
            for (char value : values) {
                combos.add(String.valueOf(value));
            }
        } else {
            Combiner other = new Combiner();
            other.accept(values);
            combine(other);
        }
    }

    public Combiner combine(Combiner other) {
        combos = stream()
                .flatMap(v1 -> other.stream().map(v2 -> v1 + v2))
                .collect(Collectors.toList());
        return this;
    }
}

本质上,这是一个收集器,它接收流中的每个元素,并为元素中的项目和现有组合的每个串联添加新组合。

以下是一些展示如何使用它的示例代码:

public static void main(String[] args) {
    char[][] vals = {{'a', 'b'}, {'c'}, {'d', 'e'}, {'f', 'g', 'h'}};

    Arrays.stream(vals).parallel()
            .collect(Combiner::new, Combiner::accept, Combiner::combine)
            .stream().forEach(System.out::println);
}

parallel并非绝对必要:它只是为了表明对于大量的组合,流可以在处理器之间拆分然后组合。

对于可以自然流式传输的其他类型的数据,代码要简单得多。不幸的是,没有Arrays.stream(char[])因此传统的迭代使代码更清晰一些。你可以使用丑陋的东西,比如转换为字符串,然后转换为IntStream,然后转到Stream<Character>,但坦率地说,要避免迭代数组需要做很多工作: - )