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

Posted

技术标签:

【中文标题】通过从数组中选择来创建排列【英文标题】: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 循环 请展示一些示例输入和输出,以便更好地理解问题 我照你说的做了,这说明清楚了吗? 本文讨论了一种排列算法。您将为每个数组重复此操作***.com/questions/2710713/… 【参考方案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 个字母的单词。

以上是关于通过从数组中选择来创建排列的主要内容,如果未能解决你的问题,请参考以下文章

通过从缩略图或下一个上一个按钮中选择来更改图像

通过从两个不同的数组中随机选择键和值将两个数组组合成一个数组

当用户通过电子邮件更新记录时,如何通过从指定表中选择记录的 status_id 来通知所有管理员

如何通过从 input type='file' 中选择来设置多个视频文件的预览

如何通过从另一个下拉列表中选择值来填充下拉列表?

添加列通过从sql Server中的另一个表中选择所有项目来选择表