【问题标题】:Creating permutations by selecting from arrays通过从数组中选择来创建排列
【发布时间】:2016-03-29 08:55:30
【问题描述】:

我有一个二维数组,用于存储与您在手机键盘上看到的内容相对应的不同字母。

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

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

为了让这个问题更容易理解,这里举个例子:

三个字母的单词从第 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

【问题讨论】:

  • 你肯定不想使用递归。这绝对是一个动态规划问题。考虑使用 for 循环
  • 请展示一些示例输入和输出,以便更好地理解问题
  • 我照你说的做了,这说明清楚了吗?
  • 本文讨论了一种排列算法。您将为每个数组重复此操作stackoverflow.com/questions/2710713/…

标签: java arrays recursion permutation


【解决方案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&lt;Character&gt;,但坦率地说,避免遍历数组需要做很多工作:-)

【讨论】:

    【解决方案2】:

    试试这个,如果你可以使用 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));
    

    【讨论】:

      【解决方案3】:

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

      您首先定义要按下的键的顺序。例如,如果您想从数组的前五行中各取一个字母,则序列将为 (1, 2, 3, 4, 5)(因为第一行为空)。如果要拼写“stack”,则顺序为 (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】:

        首先要注意的是,如果您通过从 5 行中的每一行中选择 3 个字符中的一个来造词,那么您总共会得到 35 = 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 方法,您会发现第一个在字符串完成时不会递归,而第二个因为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();
          }
        }
        

        【讨论】:

          【解决方案5】:

          这可以使用动态规划方法来解决。

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

          这个函数应该可以做到。

          【讨论】:

          • 如果超过 2 个,比如 12 个呢?我希望我的代码适用于长度为 1-12 个字母的单词。
          猜你喜欢
          • 2021-01-21
          • 1970-01-01
          • 2021-05-13
          • 1970-01-01
          • 1970-01-01
          • 2015-04-06
          • 1970-01-01
          • 2021-12-25
          • 1970-01-01
          相关资源
          最近更新 更多