公司命名

2024-09-25

题目:

给你一个字符串数组 ideas 表示在公司命名过程中使用的名字列表。公司命名流程如下:

  1. ideas 中选择 2 个 不同 名字,称为 ideaAideaB
  2. 交换 ideaAideaB 的首字母。
  3. 如果得到的两个新名字 不在 ideas 中,那么 ideaA ideaB串联 ideaAideaB ,中间用一个空格分隔)是一个有效的公司名字。
  4. 否则,不是一个有效的名字。

返回 不同 且有效的公司名字的数目。

示例 1:

输入:ideas = ["coffee","donuts","time","toffee"]
输出:6
解释:下面列出一些有效的选择方案:
- ("coffee", "donuts"):对应的公司名字是 "doffee conuts" 。
- ("donuts", "coffee"):对应的公司名字是 "conuts doffee" 。
- ("donuts", "time"):对应的公司名字是 "tonuts dime" 。
- ("donuts", "toffee"):对应的公司名字是 "tonuts doffee" 。
- ("time", "donuts"):对应的公司名字是 "dime tonuts" 。
- ("toffee", "donuts"):对应的公司名字是 "doffee tonuts" 。
因此,总共有 6 个不同的公司名字。

下面列出一些无效的选择方案:
- ("coffee", "time"):在原数组中存在交换后形成的名字 "toffee" 。
- ("time", "toffee"):在原数组中存在交换后形成的两个名字。
- ("coffee", "toffee"):在原数组中存在交换后形成的两个名字。

示例 2:

输入:ideas = ["lack","back"]
输出:0
解释:不存在有效的选择方案。因此,返回 0 。

提示:

  • 2 <= ideas.length <= 5 * 104
  • 1 <= ideas[i].length <= 10
  • ideas[i] 由小写英文字母组成
  • ideas 中的所有字符串 互不相同

暴力遍历

思路:

枚举所有可能的公司新名字:排列所有 ideas 中的字符串即可
利用哈希表判断 交换首字母后的新名字是否出现在 ideas 中
利用哈希表存储可行的答案,最后哈希表的大小就是答案

代码:

class Solution {
public:
    long long distinctNames(vector<string>& ideas) {
        // 暴力求解:利用哈希表速度判断
        unordered_set<string> setIdeas;
        for (int i = 0; i < ideas.size(); i++) {
            setIdeas.insert(ideas[i]);
        }
        unordered_set<string> ans;
        for (int i = 0; i < ideas.size(); i++) {
            for (int j = 0; j < ideas.size(); j++) {
                // ideas[i] 开头, ideas[j]收尾
                string a = ideas[i], b = ideas[j];
                char temp = a[0];
                a[0] = b[0];b[0] = temp;
                if (setIdeas.find(a) != setIdeas.end() || setIdeas.find(b) != setIdeas.end()) continue;
                string res = a + " " + b;
                if (ans.find(res) != ans.end()) continue;
                ans.insert(res);
            }
        }
        return ans.size();
    }
};

首字母分组

思路:

什么样的一对字符串无法交换首字母?

示例 1 中的 coffee 和 time,虽然这样两个字符串完全不一样,但如果交换了 coffee 和 time 的首字母,会得到字符串 toffee,它在数组 ideas 中,不符合题目要求。

又例如 ideas=[aa,ab,ac,bc,bd,be],将其分成两组:
	第一组:aa,ab,ac。
	第二组:bc,bd,be。
其中第一组内的字符串是不能交换首字母的,因为交换后字符串不变,必然在 ideas 中。第二组也同理。

考虑交换第一组的字符串和第二组的字符串,哪些是可以交换首字母的,哪些是不能交换首字母的?
	第一组的 ac 无法和第二组的任何字符串交换,因为交换后会得到 bc,它在 ideas 中。
	第二组的 bc 无法和第一组的任何字符串交换,因为交换后会得到 ac,它在 ideas 中。
	其余字符串对可以交换首字母。
上面的分析立刻引出了如下方法。

按照首字母,把 ideas 分成(至多)26 组字符串。

例如 ideas=[aa,ab,ac,bc,bd,be] 分成如下两组(只记录去掉首字母后的字符串):
	A 组(集合):{a,b,c}。
	B 组(集合):{c,d,e}。

分组后:
	从 A 中选一个不等于 c 的字符串,这有 2 种选法。
	从 B 中选一个不等于 c 的字符串,这有 2 种选法。
	考虑两个字符串的先后顺序(谁在左谁在右),有 2 种方法。
根据乘法原理,有 2×2×2=8 对符合要求的字符串。

由于无法选交集中的字符串,一般地,从 A 和 B 中可以选出 2 ⋅ (∣A∣−∣A∩B∣) ⋅ (∣B∣−∣A∩B∣)
对符合要求的字符串。其中 ∣S∣ 表示集合 S 的大小。

枚举所有组对,计算上式,累加到答案中。

代码:

class Solution {
public:
    long long distinctNames(vector<string>& ideas) {
        vector<set<string>> groupIdeas(26);
        for (int i = 0; i < ideas.size(); i++) {
            string subStr = ideas[i].substr(1);
            groupIdeas[ideas[i][0] - 'a'].insert(subStr);
        }
        long long ans = 0;
        for (int i = 0; i < 26; i++) {
            for (int j = i + 1; j < 26; j++) {
                // 求交集大小
                long long m = 0;
                for (auto& temp : groupIdeas[i]) {
                    m += groupIdeas[j].count(temp);
                }
                ans += (2 * (groupIdeas[i].size() - m) * (groupIdeas[j].size() - m) );
            }
        }
        return ans;
    }
};