我有一个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
。
答案 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);
}
答案 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>
,但坦率地说,要避免迭代数组需要做很多工作: - )