字符串ideas中有一系列的名字,用这些名字给公司命名,命名规则如下:
在ideas中选出2个不同的单词,交换它们的首字母,
交换首字母后的两个单词如果不和ideas中的任一单词相同,那么就可以用它们命名,
把它们cat起来就是公司的命名。
返回有效名字的个数。
思路:
一般的会这么想,提取每两个不同的单词,交换首字母,再看交换后的单词是不是存在于原ideas,
如果都不存在,命名个数+1,
耗时的地方在于每2个pair都要提取出来,O(n2),还要交换首字母,再判断是不是存在于原ideas.
与其每次交换首字母,不如直接比较每2个单词除了首字母之外后面的部分是否相同。
如果相同,这个单词就不考虑了。为什么呢?
首字母只有26个,作为index, 这个index处保存所有的除首字母之外的单词部分,
所以这个结构应该是HashSet[26], 每个hashSet[i] 保存去掉首字母的单词部分。
那么看HashSet[ i ]中的每个单词 是不是出现在其他的 HashSet[ j ]中,
这样做的好处是,不需要交换首字母,也会知道是不是在原ideas中存在。
比如单词ahash和thash, 它们会保存为如下结构:
a: hash
t: hash
比较发现a中的hash存在于t中, 那么ahash和thash交换首字母之后是thash和ahash, 是存在于原ideas中的。
所以只要后面部分相同,同时存在于两个首字母的结构中,交换了首字母也还是会在原ideas中存在。
这部分要从命名个数中减掉,因为不满足命名规则。
最后只需要用两两HashSet的(单词个数 - 相同的个数)相乘再乘以2,
相乘是因为两两可组成单词,乘以2是因为可以先后顺序不同组成不同的命名。
另外,不需要知道具体的命名,只需要知道个数,那么只需要统计每个HashSet的size, HashSet[ i ]和HashSet[j]中相同单词的个数。
public long distinctNames(String[] ideas) {
HashSet<String>[] set = new HashSet[26];
int[][] same = new int[26][26];
long res = 0;
for(int i = 0; i < 26; i++) set[i] = new HashSet<>();
for(String idea : ideas) {
set[idea.charAt(0)-'a'].add(idea.substring(1));
}
for(int i = 0; i < 26; i++) {
for(String word : set[i]) {
for(int j = i+1; j < 26; j++){
if(set[j].contains(word)) same[i][j] ++;
}
}
}
for(int i = 0; i < 26; i++) {
for(int j = i+1; j < 26; j++) {
res += (set[i].size() - same[i][j]) *
(set[j].size() - same[i][j]) * 2;
}
}
return res;
}