当前方法的问题/其他答案
有很多答案,但没有一个是非常有效的。
对于子字谜候选中的每个字母,我们搜索列表并删除字母。一次搜索需要线性时间。由于我们必须对每个字母进行搜索,我们最终会得到二次时间复杂度。
有些人建议使用集合而不是列表。在集合中搜索需要恒定的时间,所以我们最终会得到线性时间。但是,当同一个字母多次出现时,set 方法会失败。
由于恒速因素,所提出的解决方案也很慢。当我们使用List<Character> 或Set<Character> 时,String 的chars 必须装箱在Character 对象中。创建和处理这些对象比使用基本的char 类型要慢得多。
解决方案
多组
我们可以使用multiset(也称为bag)来表示单词中的字母。对于每个单词,我们创建其字母的多重集,并检查该多重集是否是基本单词的字母多重集的子集。
示例
基本词"Food" 有多重集合{f, o, o, d}。
Word "do" 具有多重集 {d, o}。
Word "dod" 有多重集合{d, d, o}。
{d, o} 是 {f, o, o, d} 的子集 ==> do 是 food 的子字谜。
{d, o, d} 不是 {f, o, o, d} 的子集==> dod 不是food 的子字谜。
存储多集
由于我们知道只有字符'a' 到'z' 出现,我们使用int 数组来表示一个多重集。 array[0]的值为'a's的个数; array[1] 的值为'b's 的数量,以此类推。
array[1]也可以写成array['b' - 'a']
示例
带有多重集{f, o, o, d} 的单词"Food" 由数组表示
// Entry for: a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z
int[] multiSet = {0,0,0,1,0,1,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0};
子集检查
a 是b 的子集当且仅当a[i] <= b[i] 对于所有i。
当我们在计算多重集a 时进行子集测试时,我们不必检查所有 26 个数组条目,而只需检查设置为大于零值的条目。
重用工作
我们要检查很多词中的一个基本词。我们可以将多重集重新用于基本词,而不必一遍又一遍地计算它。
我们不是编写返回 true 或 false 的方法,而是编写一个返回给定基本词和给定字典(要检查的词列表)的所有子字谜列表的方法。
小优化
如果一个词比基本词长,它就不能是一个子字谜。在这种情况下,我们不必为那个词计算多重集。
实施
public static List<String> subAnagrams(String base, List<String> dictionary) {
char[] usableChars = new char['z' - 'a'];
base = base.toLowerCase();
for (int i = 0; i < base.length(); ++i) {
++usableChars[base.charAt(i) - 'a'];
}
List<String> subAnagrams = new ArrayList<>();
for (String candidate : dictionary) {
boolean isSubAnagram = candidate.length() <= base.length();
candidate = candidate.toLowerCase();
char[] usedChars = new char['z' - 'a'];
for (int i = 0; isSubAnagram && i < candidate.length(); ++i) {
int charIndex = candidate.charAt(i) - 'a';
isSubAnagram = ++usedChars[charIndex] <= usableChars[charIndex];
}
if (isSubAnagram) {
subAnagrams.add(candidate);
}
}
return subAnagrams;
}
示例用法
public static void main(String[] args) {
List<String> dict = new ArrayList<>();
dict.add("Do");
dict.add("Odd");
dict.add("Good");
dict.add("World");
dict.add("Foo");
System.out.println(subAnagrams("Food", dict));
}
打印[do, foo]