思路:首先如果没有相同的后缀,则无论只要不是相同的首字母交换都不会出现重复情况,如果有重复后缀,则还需多增加个不能和,首字符与另一相同后缀字串的首字符相同的字串交换。
主要矛盾已经明确,则可对矛盾进行分析。 首先把范围缩小到只有两种不同首字母,对于这种情况
如果首字符相同 肯定是0,
如果没有相同后缀的情况, 为字母A开头字串集合.size() x 字母B开头字串集合.size()
如果有相同后缀的情况下 为(A开头字串集合.size()-有相同后缀的字串数) x(B开头字串集合.size()-有相同后缀的字串数)
因为一旦对有相同的后缀字符进行替换后,必定会重复,所有等同于没有这个字母
基于上述:扩展到26种首字符也相同,只是需要遍历所有组合即可
方案一:按首字母进行分组
分组完成即可遍历所有组合进行计算
class Solution {
public:
long long distinctNames(vector<string>& ideas) {
unordered_set<string> rec[26];
for (auto& s : ideas) {
rec[s[0] - 'a'].insert(s.substr(1));
}
//分组完成
long long ans = 0;
for (int a = 1; a < 26; a++) {
for (int b = 0; b < a; b++) {//遍历所有组合
int m = 0;
for (auto& s : rec[a]) {//如果有重复的累计一下
m += rec[b].count(s);
}
ans += (long long) (rec[a].size() - m) * (rec[b].size() - m);
}
}
return ans * 2; //反转也算一次
}
};
方案二:按后缀进行分组
按后缀也是同样的道理,但可使用位运算存储一个后缀,所有的全部首字母,并且优化结构,把时间均摊到,初始化阶段,时间复杂度有所降低。
long long distinctNames(vector<string>& ideas) {
int size[26]{};
int intersection[26][26]{};
unordered_map<string, int> groups;
for (auto& s : ideas) {
int b = s[0] - 'a';
size[b]++;
auto suffix = s.substr(1);
int mask = groups[suffix];
groups[suffix] = mask | 1 << b;
for (int a = 0; a < 26; a++) {
if (mask >> a & 1) {
intersection[b][a]++;
intersection[a][b]++;
}
}
}
long long ans = 0;
for (int a = 1; a < 26; a++) {
for (int b = 0; b < a; b++) {
int m = intersection[a][b];
ans += (long long) (size[a] - m) * (size[b] - m);
}
}
return ans * 2;
}