繁体   English   中英

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

[英]Creating permutations by selecting from arrays

我有一个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行的字母, {'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行中的每一行中选择3个字符中的一个来制作单词,那么你将总共得到3 5 = 243个单词。 无论您如何实施该计划,它必须最终构建这243个单词。

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

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

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

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

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

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

permuter.makeWord(1, 5);

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

import java.util.*;

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

  StringBuffer wordBuffer = new StringBuffer();
  ArrayList<String> words = new ArrayList<String>();

  void makeWord(int currentRowIndex, int endRowIndex) {
    char[] row = rows[currentRowIndex];
    for (int i = 0; i < row.length; ++i) {
      wordBuffer.append(row[i]);
      if (currentRowIndex == endRowIndex) {
        words.add(wordBuffer.toString());
      } else {
        makeWord(currentRowIndex + 1, endRowIndex);
      }
      wordBuffer.deleteCharAt(wordBuffer.length() - 1);
    }
  }

  void makeWord(int[] indices, int position) {
    if (position == indices.length) {
      words.add(wordBuffer.toString());
      return;
    }
    char[] row = rows[indices[position]];
    for (int i = 0; i < row.length; ++i) {
      wordBuffer.append(row[i]);
      makeWord(indices, position + 1);
      wordBuffer.deleteCharAt(wordBuffer.length() - 1);
    }
  }

  void displayWords() {
    if (words.size() != 0) {
      System.out.print(words.get(0));
      for (int i = 1; i < words.size(); ++i) {
        System.out.print(" " + words.get(i));
      }
      System.out.println();
    }
    System.out.println(words.size() + " words");
  }

  public static void main(String[] args) {
    PermuteCharacters permuter = new PermuteCharacters();
    permuter.makeWord(1, 5);
    permuter.displayWords();
  }
}

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

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));
}

并称之为。

    permutation(convert, 5)
        .forEach(x -> System.out.println(x));

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

取前两行并组合数组中的所有字符串。 这将给你一个大小为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;
}

这个功能应该这样做。

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

首先定义要按下的键序列。 例如,如果要从数组的前五行各取一个字母,则序列将为(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);
}

运行

这是使用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>但坦率地说,为避免迭代数组,需要做很多工作:-)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM