java使用给定的单词列表重新创建给定字符串的多种方法
给定的是一个字符串word
和一个包含一些字符串的字符串数组book
。程序应该给出仅使用book
中的元素创建word
的可能性数量。一个元素可以使用任意次数,程序必须在6秒内终止
例如,输入:
String word = "stackoverflow";
String[] book = new String[9];
book[0] = "st";
book[1] = "ck";
book[2] = "CAG";
book[3] = "low";
book[4] = "TC";
book[5] = "rf";
book[6] = "ove";
book[7] = "a";
book[8] = "sta";
输出应该是2
,因为我们可以通过两种方式创建"stackoverflow"
:
1:"st"
+"a"
+"ck"
+"ove"
+"rf"
+"low"
2:"sta"
+"ck"
+"ove"
+"rf"
+"low"
如果word
相对较小(<;15个字符),我的程序实现仅在所需时间内终止。但是,正如我前面提到的,程序的运行时间限制是6秒,它应该能够处理非常大的word
字符串(>;1000个字符)Here是大型输入的一个示例
这是我的密码:
1)实际方法:
输入:字符串word
和字符串[]book
输出:仅使用书本中的字符串写入单词的方式数
public static int optimal(String word, String[] book){
int count = 0;
List<List<String>> allCombinations = allSubstrings(word);
List<String> empty = new ArrayList<>();
List<String> wordList = Arrays.asList(book);
for (int i = 0; i < allCombinations.size(); i++) {
allCombinations.get(i).retainAll(wordList);
if (!sumUp(allCombinations.get(i), word)) {
allCombinations.remove(i);
allCombinations.add(i, empty);
}
else count++;
}
return count;
}
2)所有子字符串():
输入:字符串input
输出:一个列表列表,每个列表包含一组子字符串,这些子字符串加起来就是输入
static List<List<String>> allSubstrings(String input) {
if (input.length() == 1) return Collections.singletonList(Collections.singletonList(input));
List<List<String>> result = new ArrayList<>();
for (List<String> temp : allSubstrings(input.substring(1))) {
List<String> firstList = new ArrayList<>(temp);
firstList.set(0, input.charAt(0) + firstList.get(0));
if (input.startsWith(firstList.get(0), 0)) result.add(firstList);
List<String> l = new ArrayList<>(temp);
l.add(0, input.substring(0, 1));
if (input.startsWith(l.get(0), 0)) result.add(l);
}
return result;
}
(三)总结():
输入:字符串列表input
和字符串expected
输出:如果input
中的元素加起来等于expected
,则为true
public static boolean sumUp (List<String> input, String expected) {
String x = "";
for (int i = 0; i < input.size(); i++) {
x = x + input.get(i);
}
if (expected.equals(x)) return true;
return false;
}
# 1 楼答案
我发现我在my previous answer中做错了什么:我没有使用回忆录,所以我在重做大量不必要的工作
考虑一个书数组^ {< CD1>}和一个目标词^ {< CD2>}。有四种方法可以构建这个目标:
我之前的尝试将分别通过这四个步骤。但是,我们可以观察到:
"a"
"aa"
,要么"a" + "a"
要么直接使用"aa"
李>"aaa"
来构造"aaa"
(1种方式);或者"aa" + "a"
(两种方式,因为有两种方式来构建"aa"
);或者"a" + "aa"
(单向)李>请注意,这里的第三步只在先前构造的字符串中添加了一个额外的字符串,我们知道可以用多少种方法构造它
这表明,如果我们计算
word
前缀的构造方式的数量,我们可以通过从book
中再添加一个字符串来计算更长前缀的方式的数量我定义了一个简单的trie类,因此您可以快速查找
book
单词的前缀,这些前缀在word
中的任何给定位置都匹配:对于
s
中的每个字母,这将创建TrieNode
的一个实例,并为后续字符等存储TrieNode
对于very long input given by OP,这将提供以下输出:
比所需的6秒要快很多——这也包括JVM启动时间
编辑:事实上,trie不是必需的。您可以简单地用以下内容替换“在trie中行走”循环:
它的运行速度比6s慢,但仍然快得多:
# 2 楼答案
以下是一些观察:
在循环时,使用String+不是一个好主意。使用StringBuilder并附加到循环中的StringBuilder,最后
return builder.toString()
。或者你跟随安迪的想法。不需要合并字符串,您已经知道目标单词。见下文然后:
List
意味着添加/删除元素可能代价高昂。所以,看看你是否能摆脱这一部分,如果有可能使用地图,集代替最后:真正的重点是研究你的算法。我会努力“向后”工作。意思:首先确定那些在目标词中实际出现的数组元素。你可以从一开始就忽略其他人
然后:查看**开始*+搜索词的所有数组条目。在您的示例中,您可以注意到只有两个适合的数组元素。然后从那里开始工作
# 3 楼答案
我认为你在优化应用程序方面已经做得很好了。除了幽灵猫的回答之外,我还有几点建议:
由于您对查看方法的执行时间感兴趣,我建议您实施某种基准测试系统。下面是一个快速的模型:
输出
# 4 楼答案
注意:这个实现被@user1221提到的测试用例卡住了,正在处理它
我能想到的是一种基于Trie的方法,即
O(sum of length of words in dict)
空间。时间不是最佳选择程序:
O(sum of lengths of all strings in dict)
李>stackoverflow
,我们检查是否到达任何字符串的末尾,如果是,则我们到达该字符串的有效组合。我们在返回递归时计算这个李>例如: 在上面的例子中,我们使用dict作为
{"st", "sta", "a", "ck"}
我们构造我们的trie($
是sentinel字符,即不在dict中的字符):.
表示dict中的一个单词在该位置结束。 我们试图找出stack
结构的数量我们开始在trie中搜索
stack
我们看到我们在一个单词的末尾,我们用顶部剩余的字符串
ack
开始新的搜索再一次,我们在dict中一个单词的末尾。我们开始新的
ck
搜索我们到达了
stack
的结尾和dict中一个单词的结尾,因此我们有一个有效的stack
表示我们回到调用
depth=2
没有下一个字符可用,我们返回
depth=1
的调用者我们转到下一个字符。我们看到我们到达了dict中一个单词的末尾,我们在dict中启动了一个新的搜索
ck
我们到达了
stack
的结尾,并且在dict中完成了一个工作,所以又是一个有效的表示。我们回到调用depth=1
没有更多的字符要继续,我们返回结果
2
<强>注:实现是C++,不太难转换为java,这个实现假定所有字符都是小写的,将它扩展到两种情况都很微不足道。p>
示例代码(full version):
# 5 楼答案
我的第一个观察结果是,你实际上不需要构建任何东西:你知道你要构建什么字符串(例如^{),所以你真正需要跟踪的是你到目前为止匹配了多少字符串。称之为
m
接下来,在匹配了
m
个字符之后,提供了m < word.length()
,您需要从book
中选择下一个字符串,该字符串匹配word
从m
到m + nextString.length()
的部分可以通过依次检查每个字符串来实现:
但是,通过提前确定无法匹配的字符串,您可以做得更好:您附加的下一个字符串将具有以下属性:
word.charAt(m) == nextString.charAt(0)
(下一个字符匹配)m + nextString.length() <= word.length()
(添加下一个字符串不应使构造的字符串超过word
)所以,你可以通过构建一个字母到以字母开头的单词的映射图(第1点)来减少书中可能要检查的潜在单词;如果你把同一个起始字母的单词按长度递增的顺序存储起来,一旦长度过大,你就可以停止检查这个字母(第2点)
您可以一次性构建地图并重复使用:
您可以递归地计算方法的数量,而无需构造任何其他对象(*):
(*)这可能会构造
Character
的新实例,因为word.charAt(m)
的装箱:缓存实例保证仅用于0-127范围内的字符。有很多方法可以解决这个问题,但它们只会让代码变得混乱