|
| 1 | +## 题目地址(2306. 公司命名) |
| 2 | + |
| 3 | +https://leetcode.cn/problems/naming-a-company/ |
| 4 | + |
| 5 | +## 题目描述 |
| 6 | + |
| 7 | +``` |
| 8 | +给你一个字符串数组 ideas 表示在公司命名过程中使用的名字列表。公司命名流程如下: |
| 9 | +
|
| 10 | +从 ideas 中选择 2 个 不同 名字,称为 ideaA 和 ideaB 。 |
| 11 | +交换 ideaA 和 ideaB 的首字母。 |
| 12 | +如果得到的两个新名字 都 不在 ideas 中,那么 ideaA ideaB(串联 ideaA 和 ideaB ,中间用一个空格分隔)是一个有效的公司名字。 |
| 13 | +否则,不是一个有效的名字。 |
| 14 | +
|
| 15 | +返回 不同 且有效的公司名字的数目。 |
| 16 | +
|
| 17 | + |
| 18 | +
|
| 19 | +示例 1: |
| 20 | +
|
| 21 | +输入:ideas = ["coffee","donuts","time","toffee"] |
| 22 | +输出:6 |
| 23 | +解释:下面列出一些有效的选择方案: |
| 24 | +- ("coffee", "donuts"):对应的公司名字是 "doffee conuts" 。 |
| 25 | +- ("donuts", "coffee"):对应的公司名字是 "conuts doffee" 。 |
| 26 | +- ("donuts", "time"):对应的公司名字是 "tonuts dime" 。 |
| 27 | +- ("donuts", "toffee"):对应的公司名字是 "tonuts doffee" 。 |
| 28 | +- ("time", "donuts"):对应的公司名字是 "dime tonuts" 。 |
| 29 | +- ("toffee", "donuts"):对应的公司名字是 "doffee tonuts" 。 |
| 30 | +因此,总共有 6 个不同的公司名字。 |
| 31 | +
|
| 32 | +下面列出一些无效的选择方案: |
| 33 | +- ("coffee", "time"):在原数组中存在交换后形成的名字 "toffee" 。 |
| 34 | +- ("time", "toffee"):在原数组中存在交换后形成的两个名字。 |
| 35 | +- ("coffee", "toffee"):在原数组中存在交换后形成的两个名字。 |
| 36 | +
|
| 37 | +
|
| 38 | +示例 2: |
| 39 | +
|
| 40 | +输入:ideas = ["lack","back"] |
| 41 | +输出:0 |
| 42 | +解释:不存在有效的选择方案。因此,返回 0 。 |
| 43 | +
|
| 44 | +
|
| 45 | + |
| 46 | +
|
| 47 | +提示: |
| 48 | +
|
| 49 | +2 <= ideas.length <= 5 * 104 |
| 50 | +1 <= ideas[i].length <= 10 |
| 51 | +ideas[i] 由小写英文字母组成 |
| 52 | +ideas 中的所有字符串 互不相同 |
| 53 | +``` |
| 54 | + |
| 55 | +## 前置知识 |
| 56 | + |
| 57 | +- 枚举 |
| 58 | +- 笛卡尔积 |
| 59 | + |
| 60 | +## 公司 |
| 61 | + |
| 62 | +- 暂无 |
| 63 | + |
| 64 | +## 思路 |
| 65 | + |
| 66 | +为了方便描述,我们称 idea 的首字母为 idea 的前缀,除了首字母的其余部分称为 idea 的后缀。 |
| 67 | + |
| 68 | +最简单的暴力思路就是直接模拟。 |
| 69 | + |
| 70 | +枚举 ideas, 对于每一个 idea,我们可以将其替换为任意不等于 idea[0] 的字母 ch。 |
| 71 | + |
| 72 | +如果同时满足以下两个条件: |
| 73 | + |
| 74 | +1. ch + idea[1:] 不在 ideas 中 |
| 75 | +2. idea[0] + b 不在 ideas 中。其中 b 指的是和 idea 有公共后缀的后缀。 |
| 76 | + |
| 77 | +由于需要枚举前后缀,因此我们可以先用字典预处理出所有的后缀,key 为前缀,value 为后缀,含义为前缀为 key 的后缀集合。 |
| 78 | + |
| 79 | +比如 ideas = ["coffee","donuts","time","toffee"] 会预处理为: |
| 80 | + |
| 81 | +```py |
| 82 | +c: set(["offee"]) |
| 83 | +d: set(["onuts"]) |
| 84 | +t: set(["ime", "offee"]) |
| 85 | + |
| 86 | +``` |
| 87 | + |
| 88 | +则将其将入到哈希集合中,最后返回哈希集合的大小即可。 |
| 89 | + |
| 90 | +暴力法代码: |
| 91 | + |
| 92 | +```py |
| 93 | +class Solution: |
| 94 | + def distinctNames(self, ideas: List[str]) -> int: |
| 95 | + ans = set() |
| 96 | + seen = set(ideas) |
| 97 | + starts = collections.defaultdict(list) |
| 98 | + # 预处理出 starts 字典 |
| 99 | + for idea in ideas: |
| 100 | + starts[idea[0]].append(idea[1:]) |
| 101 | + |
| 102 | + for idea in ideas: |
| 103 | + for i in range(26): |
| 104 | + ch = chr(i + 97) |
| 105 | + if idea[0] != ch: |
| 106 | + a = ch + idea[1:] |
| 107 | + if a not in seen: |
| 108 | + # 枚举后缀 |
| 109 | + for b in starts[ch]: |
| 110 | + if idea[0] + b not in seen: |
| 111 | + ans.add((a, idea[0] + b)) |
| 112 | + return len(ans) |
| 113 | + |
| 114 | +``` |
| 115 | + |
| 116 | +暴力法会超时,原因在于时间复杂度为 ${O(n^2)}$,代入题目的 $5 * 10^4$ 的数据规模是通过不了的。如果想通过,需要 $O(nlogn)$ 或者 $O(n)$ 的复杂度才行。 |
| 117 | + |
| 118 | +如何优化呢? |
| 119 | + |
| 120 | +我们前面枚举的是 idea, 实际上我们可以只枚举前缀即可。 |
| 121 | + |
| 122 | +ideaA 和 ideaB 的前缀组合一共有 $C_{2}^{26}$ 即 `26 * 25 / 2` 种。 |
| 123 | + |
| 124 | +接下来,对于以 ideaA[0] 开头的后缀列表 set_x 即 starts[ideaA[0]] 和 ideaB[0] 开头的后缀列表 set_y 即 starts[ideaB[0]]。那么如何组合才能是有效的名字呢? |
| 125 | + |
| 126 | +ideaA[0] 想和 set_y 进行组合,有两个问题。 |
| 127 | + |
| 128 | +1. 如何组合? |
| 129 | + |
| 130 | +枚举 set_x 中的后缀,然后枚举 set_y 两两组合即可,本质上就是 set_x 和 set_y 两个集合的笛卡尔。 |
| 131 | + |
| 132 | +2. 组合后哪些是无效,哪些是有效的? |
| 133 | + |
| 134 | +根据题目要求,应该是**得到的两个新名字至少有一个在 ideas 中**。其实就是说如果 set_x 中的后缀 a 在 set_y 中存在就是无效的。反之 set_y 中的后缀 b 在 set_x 中存在也是无效的。 |
| 135 | + |
| 136 | +也就是说,set_x 和 set_y 的差集和 set_x 和 set_y 的补集的笛卡尔积的两倍就是答案。两倍的原因是顺序是重要的,顺序不同会被认为是两个有效名字。 |
| 137 | + |
| 138 | +> 需要特别注意的是由于 idea 中没有空格,因此拼接出来的公司名一定不在 ideas 中。 |
| 139 | +
|
| 140 | +## 关键点 |
| 141 | + |
| 142 | +- |
| 143 | + |
| 144 | +## 代码 |
| 145 | + |
| 146 | +- 语言支持:Python3 |
| 147 | + |
| 148 | +Python3 Code: |
| 149 | + |
| 150 | +```python |
| 151 | + |
| 152 | +class Solution: |
| 153 | + def distinctNames(self, ideas: List[str]) -> int: |
| 154 | + ans = 0 |
| 155 | + seen = set(ideas) |
| 156 | + starts = collections.defaultdict(set) |
| 157 | + |
| 158 | + for idea in ideas: |
| 159 | + starts[idea[0]].add(idea[1:]) |
| 160 | + for j in range(25): |
| 161 | + for i in range(j + 1, 26): |
| 162 | + set_x = starts[chr(i + 97)] |
| 163 | + set_y = starts[chr(j + 97)] |
| 164 | + intersections = len(set_x & set_y) # 交集 |
| 165 | + ans += 2 * (len(set_x) - intersections) * (len(set_y) - intersections) |
| 166 | + return ans |
| 167 | + |
| 168 | + |
| 169 | +``` |
| 170 | + |
| 171 | +**复杂度分析** |
| 172 | + |
| 173 | +令 n 为数组长度。 |
| 174 | + |
| 175 | +- 时间复杂度:$O(n)$ |
| 176 | +- 空间复杂度:$O(n)$ |
| 177 | + |
| 178 | +> 此题解由 [力扣刷题插件](https://leetcode-pp.github.io/leetcode-cheat/?tab=solution-template) 自动生成。 |
| 179 | +
|
| 180 | +力扣的小伙伴可以[关注我](https://leetcode-cn.com/u/fe-lucifer/),这样就会第一时间收到我的动态啦~ |
| 181 | + |
| 182 | +以上就是本文的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 48K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。 |
| 183 | + |
| 184 | +关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。 |
| 185 | + |
| 186 | + |
0 commit comments