classSolution: defdistinctNames(self, ideas: List[str]) -> int: group = defaultdict(int) for s in ideas: group[s[1:]] |= 1 << (ord(s[0]) - ord('a')) ans = 0 cnt = [[0] * 26for _ inrange(26)] for mask in group.values(): for i inrange(26): if mask >> i & 1 == 0: for j inrange(26): if mask >> j & 1: cnt[i][j] += 1 else: for j inrange(26): if mask >> j & 1 == 0: ans += cnt[i][j] return ans * 2
funcdistinctNames(ideas []string) (ans int64) { group := map[string]int{} for _, s := range ideas { group[s[1:]] |= 1 << (s[0] - 'a') } cnt := [26][26]int{} for _, mask := range group { for i := 0; i < 26; i++ { if mask>>i&1 == 0 { for j := 0; j < 26; j++ { if mask>>j&1 > 0 { cnt[i][j]++ } } } else { for j := 0; j < 26; j++ { if mask>>j&1 == 0 { ans += int64(cnt[i][j]) } } } } } return ans * 2 }
第二种实现方式是根据首字母分组。
对于 A 组中的字符串 s,如果 B 组中存在字符串 t,使得 s[1:]=t[1:],那么 s 无法与 B 组中的任意字符串互换首字母,否则可以互换。对于 B 组同理。
设 A 组和 B 组交集的大小为 m,则这两个组可以组成的合法答案数为
2\cdot(|A|-m)\cdot(|B|-m)
其中 |A| 表示集合 A 的大小,|B| 表示集合 B 的大小。
遍历所有组对,累加答案。
注:相比上面的写法,这种写法会让 Python 跑的飞快,但是其他语言并无太大区别。
[sol2-Python3]
1 2 3 4 5 6 7 8 9 10
classSolution: defdistinctNames(self, ideas: List[str]) -> int: group = defaultdict(set) for s in ideas: group[s[0]].add(s[1:]) ans = 0 for a, b in combinations(group.values(), 2): m = len(a & b) ans += (len(a) - m) * (len(b) - m) return ans * 2
[sol2-Java]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
classSolution { publiclongdistinctNames(String[] ideas) { vargroup=newSet[26]; for (vari=0; i < 26; i++) group[i] = newHashSet<String>(); for (var s : ideas) group[s.charAt(0) - 'a'].add(s.substring(1)); varans=0L; for (vari=1; i < 26; ++i) for (varj=0; j < i; ++j) { varm=0; for (var s : group[i]) if (group[j].contains(s)) ++m; ans += (long) (group[i].size() - m) * (group[j].size() - m); } return ans * 2; } }
[sol2-C++]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
classSolution { public: longlongdistinctNames(vector<string> &ideas){ unordered_set<string> group[26]; for (auto &s : ideas) group[s[0] - 'a'].emplace(s.substr(1)); long ans = 0L; for (int i = 1; i < 26; ++i) for (int j = 0; j < i; ++j) { int m = 0; for (auto &s : group[i]) m += group[j].count(s); ans += (long) (group[i].size() - m) * (group[j].size() - m); } return ans * 2; } };
funcdistinctNames(ideas []string) (ans int64) { group := [26]map[string]bool{} for i := range group { group[i] = map[string]bool{} } for _, s := range ideas { group[s[0]-'a'][s[1:]] = true } for i, a := range group { for _, b := range group[:i] { m := 0 for s := range a { if b[s] { m++ } } ans += int64(len(a)-m) * int64(len(b)-m) } } return ans * 2 }
第三种实现方式结合了上面两种写法,在遍历 ideas 的同时,计算上面写法中的交集大小 m。
具体地,按照除去首字母的子串 ideas}[i][1:] 分组,记录每组的首字母,同时统计字符 i 无法与多少个字符 j 开头的字符串交换。
classSolution: defdistinctNames(self, ideas: List[str]) -> int: group = defaultdict(int) size = [0] * 26 bad = [[0] * 26for _ inrange(26)] for s in ideas: i = ord(s[0]) - ord('a') s = s[1:] mask = group[s] group[s] |= 1 << i size[i] += 1 for j inrange(26): if mask >> j & 1: bad[i][j] += 1# 统计 i 无法与多少个 j 开头的字符串交换 bad[j][i] += 1# 统计 j 无法与多少个 i 开头的字符串交换 ans = 0 for i, b inenumerate(bad): for j, m inenumerate(b[:i]): ans += (size[i] - m) * (size[j] - m) return ans * 2
funcdistinctNames(ideas []string) (ans int64) { group := map[string]int{} size := [26]int{} bad := [26][26]int{} for _, s := range ideas { i := s[0] - 'a' mask := group[s[1:]] group[s[1:]] |= 1 << i size[i]++ for j := 0; j < 26; j++ { if mask>>j&1 > 0 { bad[i][j]++ // 统计 i 无法与多少个 j 开头的字符串交换 bad[j][i]++ // 统计 j 无法与多少个 i 开头的字符串交换 } } } for i, b := range bad { for j, m := range b[:i] { ans += int64(size[i]-m) * int64(size[j]-m) } } return ans * 2 }