Skip to content

Commit 831123e

Browse files
author
robot
committed
feat: $2306
1 parent f17c38e commit 831123e

File tree

3 files changed

+189
-1
lines changed

3 files changed

+189
-1
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,8 @@ leetcode 题解,记录自己的 leetcode 解题之路。
536536
- [2030. 含特定字母的最小子序列](./problems/2030.smallest-k-length-subsequence-with-occurrences-of-a-letter.md)
537537
- [2102. 序列顺序查询](./problems/2102.sequentially-ordinal-rank-tracker.md)
538538
- [2209. 用地毯覆盖后的最少白色砖块](./problems/2209.minimum-white-tiles-after-covering-with-carpets.md) 👍
539-
- [2281.sum-of-total-strength-of-wizards](./problems/2281.sum-of-total-strength-of-wizards.md)
539+
- [2281. 巫师的总力量和](./problems/2281.sum-of-total-strength-of-wizards.md)
540+
- [2306. 公司命名](./problems/2306.naming-a-company.md) 枚举优化好题
540541
- [5999. 统计数组中好三元组数目](./problems/5999.count-good-triplets-in-an-array.md) 👍
541542

542543
## :trident:  anki 卡片

SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@
352352
- [2102. 序列顺序查询](./problems/2102.sequentially-ordinal-rank-tracker.md)
353353
- [2209. 用地毯覆盖后的最少白色砖块](./problems/2209.minimum-white-tiles-after-covering-with-carpets.md)
354354
- [2281.sum-of-total-strength-of-wizards](./problems/2281.sum-of-total-strength-of-wizards.md)
355+
- [2306. 公司命名](./problems/2306.naming-a-company.md) 枚举优化好题
355356
- [5999. 统计数组中好三元组数目](./problems/5999.count-good-triplets-in-an-array.md) 👍
356357

357358
- [后序](epilogue.md)

problems/2306.naming-a-company.md

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
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+
![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)

0 commit comments

Comments
 (0)