题干描述
给你一个字符串数组 ideas
表示在公司命名过程中使用的名字列表。公司命名流程如下:
- 从
ideas
中选择 2 个 不同 名字,称为ideaA
和ideaB
。 - 交换
ideaA
和ideaB
的首字母。 - 如果得到的两个新名字 都 不在
ideas
中,那么ideaA ideaB
(串联ideaA
和ideaB
,中间用一个空格分隔)是一个有效的公司名字。 - 否则,不是一个有效的名字。
返回 不同 且有效的公司名字的数目。
示例 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 。
题干分析
已知该算法题的目标是计算可以生成的不同名称组合的数量。具体来说就是给定一组字符串"ideas",每个字符串可以通过交换首字母的的方式与另一个字符串组合形成新的字符串对。要求计算总共可以生成多少个不同的字符串对,且这两个字符串组合后形成的新字符串不能出现在原始的字符串列表中。
解题思路
1.分组处理:
- 将所有的字符串按照他们的首字母进行分类。假设字符串有26个字母组成,可以将所有字符串按首字母a-z分成26个组。每组中的字符串去掉首字母后,只保留其余部分。
2.交集与组合计算:
- 对每一组,将其与其他组进行比较,找出可以交换的字符串对。
- 如果两个组有相同的尾部字符串,则这些字符串不能进行交换。因此我们需要计算两组间尾部字符串的交集大小。
- 对于没有交集的字符串对,恶意直接交换首字母,生成合法的字符串对。
3.使用哈希表
- 为了快速查找和操作相同尾部的字符串,使用哈希表来存储去掉首字母后的字符串。通过比较两个哈希表中的字符串,计算每两个组间的交集。
详细步骤
1.创建哈希表
- 我们为每个首字母维护一个哈希表,哈希表中的key为去掉首字母后的字符串,value可以是一个空值,因为我们只关心字符串是否存在。
2.遍历每一个字符串
- 根据字符串的首字母,将其分类到对应的哈希表,哈希表的key是去掉首字母后的字符串。
3.两两组比较
- 计算组之间的交集大小,即两个哈希表中相同的字符串数目。
- 计算两个组之间可以生成的不同字符串对数。
4.结果累加
- 累加所有合法字符串对的数量。
5.释放内存
- 遍历结束后,释放所有哈希表的内存。
代码解释
cpp
#define MAX_STR_SIZE 16
typedef struct {
char key[MAX_STR_SIZE]; // 字符串的key,存放去掉首字母后的字符串
UT_hash_handle hh; // 哈希表句柄
} HashItem;
// 查找哈希表中的指定key项
HashItem *hashFindItem(HashItem **obj, const char* key) {
HashItem *pEntry = NULL;
HASH_FIND_STR(*obj, key, pEntry); // 在哈希表中查找指定key
return pEntry;
}
// 向哈希表中添加一个新的key
bool hashAddItem(HashItem **obj, const char* key) {
if (hashFindItem(obj, key)) { // 如果已经存在,返回false
return false;
}
HashItem *pEntry = (HashItem *)malloc(sizeof(HashItem)); // 分配内存
sprintf(pEntry->key, "%s", key); // 将key复制到哈希表项中
HASH_ADD_STR(*obj, key, pEntry); // 向哈希表中添加key
return true;
}
// 释放哈希表
void hashFree(HashItem **obj) {
HashItem *curr = NULL, *tmp = NULL;
HASH_ITER(hh, *obj, curr, tmp) { // 迭代哈希表中的所有项
HASH_DEL(*obj, curr); // 删除当前项
free(curr); // 释放内存
}
}
// 计算两个哈希表的交集大小
size_t get_intersect_size(const HashItem *a, const HashItem *b) {
size_t ans = 0;
for (HashItem *pEntry = a; pEntry; pEntry = pEntry->hh.next) { // 遍历哈希表a
if (hashFindItem(&b, pEntry->key)) { // 如果b中也有相同的key
++ans; // 交集大小加1
}
}
return ans;
}
// 计算不同的名字组合数
long long distinctNames(char** ideas, int ideasSize) {
HashItem *names[26] = {NULL}; // 存放26个字母对应的哈希表
for (int i = 0; i < ideasSize; i++) {
hashAddItem(&names[ideas[i][0] - 'a'], ideas[i] + 1); // 根据首字母添加到对应哈希表
}
long long ans = 0;
for (int i = 0; i < 26; i++) {
if (names[i] == NULL) { // 如果当前字母组为空,跳过
continue;
}
int lenA = HASH_COUNT(names[i]); // 获取当前组的字符串数量
for (int j = i + 1; j < 26; j++) {
if (names[j] == NULL) { // 如果对比的字母组为空,跳过
continue;
}
int lenB = HASH_COUNT(names[j]); // 获取对比组的字符串数量
int intersect = get_intersect_size(names[i], names[j]); // 计算两个组的交集
ans += 2 * (lenA - intersect) * (lenB - intersect); // 计算合法的字符串对数
}
}
for (int i = 0; i < 26; i++) {
hashFree(&names[i]); // 释放哈希表
}
return ans;
}