题干描述:
给定一个列表 accounts
,每个元素 accounts[i]
是一个字符串列表,其中第一个元素 accounts[i][0]
是 名称 (name),其余元素是 emails 表示该账户的邮箱地址。
现在,我们想合并这些账户。如果两个账户都有一些共同的邮箱地址,则两个账户必定属于同一个人。请注意,即使两个账户具有相同的名称,它们也可能属于不同的人,因为人们可能具有相同的名称。一个人最初可以拥有任意数量的账户,但其所有账户都具有相同的名称。
合并账户后,按以下格式返回账户:每个账户的第一个元素是名称,其余元素是 按字符 ASCII 顺序排列 的邮箱地址。账户本身可以以 任意顺序 返回。
示例 1:
输入:accounts = [["John", "[email protected]", "[email protected]"], ["John", "[email protected]"], ["John", "[email protected]", "[email protected]"], ["Mary", "[email protected]"]] 输出:[["John", '[email protected]', '[email protected]', '[email protected]'], ["John", "[email protected]"], ["Mary", "[email protected]"]] 解释: 第一个和第三个 John 是同一个人,因为他们有共同的邮箱地址 "[email protected]"。 第二个 John 和 Mary 是不同的人,因为他们的邮箱地址没有被其他帐户使用。 可以以任何顺序返回这些列表,例如答案 [['Mary','[email protected]'],['John','[email protected]'], ['John','[email protected]','[email protected]','[email protected]']] 也是正确的。
示例 2:
输入:accounts = [["Gabe","[email protected]","[email protected]","[email protected]"],["Kevin","[email protected]","[email protected]","[email protected]"],["Ethan","[email protected]","[email protected]","[email protected]"],["Hanzo","[email protected]","[email protected]","[email protected]"],["Fern","[email protected]","[email protected]","[email protected]"]] 输出:[["Ethan","[email protected]","[email protected]","[email protected]"],["Gabe","[email protected]","[email protected]","[email protected]"],["Hanzo","[email protected]","[email protected]","[email protected]"],["Kevin","[email protected]","[email protected]","[email protected]"],["Fern","[email protected]","[email protected]","[email protected]"]]
题干分析:
题干描述:
给定一个列表 accounts
,每个元素 accounts[i]
是一个字符串列表,其中第一个元素 accounts[i][0]
是名称(name),其余元素是邮箱地址(emails),表示该账户的邮箱地址。
目标是合并这些账户,如果两个账户有共同的邮箱地址,则认为这两个账户属于同一个人。即使两个账户具有相同的名称,它们也可能属于不同的人,因为人们可能具有相同的名称。
合并后的账户要求如下:
- 每个账户的第一个元素是名称。
- 其余元素是按字符 ASCII 顺序排列的邮箱地址。
- 账户本身可以以任意顺序返回。
题目理解:
题目要求将多个账号合并,如果两个账户有共同的账号合并,如果两个账户有共同的邮箱地址则视为同一账户。解决这个问题的思路主要有以下几个步骤:
1.数据结构选择:
- 使用并查集来管理邮箱的合并操作。
- 使用哈希表来建立邮箱到ID以及邮箱到用户名的映射。
2.初始化并查集和哈希表
- 初始化并查集,用于处理邮箱之间的合并操作。
- 初始化哈希表,用于邮箱到ID的映射以及邮箱到用户名的映射。
3.处理输入数据:
- 遍历输入的账户列表,对每个账户的每个邮箱进行处理。
- 为每个邮箱分配一个唯一的ID,并将该邮箱与其所属账户的第一个邮箱进行合并操作。
4.建立邮箱与其根节点的映射:
- 再次遍历所有邮箱,通过并查集找到每个邮箱的根节点,将同一个根节点的邮箱合并在一起。
5.构建最终结果
- 根据每个根节点对应的邮箱列表,构建最终的结果列表,并进行排序。
6.释放内存
- 释放并查集和哈希表占用的内存。
解题思路:
1.定义并查集结构:
UnionFind结构体包含两个数组:parent和rank,分别用于存储每个节点的父节点和排名。
//定义并查集结构
typedef struct {
int* parent;//定义父节点数组
int* rank;//定义排名数组用于优化合并操作
}UnionFind;
2.初始化并查集:
createUnionFind函数初始化并查集,设置每个节点的父节点为其自身,排名为0。
//初始化并查集
UnionFind* createUnionFind(int size) {
UnionFind* uf = (UnionFind*)malloc(sizeof(UnionFind));
uf->parent = (int*)malloc(size * sizeof(int));
uf->rank = (int*)malloc(size * sizeof(int));
//将每个节点的父节点初始化为其自身,将每个节点的排名初始化为0
for (int i = 0; i < size; i++)
{
uf->parent[i] = i;
uf->rank[i] = 0;
}
return uf;
}
3.查找根节点:
find函数通过路径压缩优化查找根节点。
//定义一个函数用于查找根节点,带路径压缩优化
int find(UnionFind* uf, int x) {
if (uf->parent[x] != x)
{
uf->parent[x] = find(uf, uf->parent[x]);//路径压缩
}
}
4.合并两个集合:
unionSets函数通过按秩合并优化合并两个集合。
//定义一个函数用于合并两个集合,并按秩合并优化
void unionSets(UnionFind* uf, int x, int y) {
int rootX = find(uf, x);
int rootY = find(uf, y);
if (rootX != rootY) {
if (uf->rank[rootX] > uf->rank[rootY])
{
uf->parent[rootY] = rootX;
}
else if (uf->rank[rootX] < uf->rank[rootY]) {
uf->parent[rootX] = rootY;
}
else
{
uf->parent[rootY] = rootX;
uf->parent[rootX]++;
}
}
}
5.哈希表节点和结构定义:
HashNode
和 HashMap
用于定义哈希表节点和哈希表结构。
//定义一个函数将并查集释放掉
void freeUnionFind(UnionFind* uf) {
free(uf->parent);
free(uf->rank);
free(uf);
}
//定义一个哈希表节点结构
typedef struct HashNode {
char* key;//哈希表的键
int value;//定义哈希表的值
struct HashNode* next;//定义链表中的下一个指针
};
//定义哈希表的结构
typedef struct {
int size;//定义哈希表的大小
HashNode** table;//定义指向哈希表数组的指针
}HashMap;
6.创建哈希表:
createHashMap
函数创建并初始化哈希表。
// 创建哈希表
HashMap* createHashMap(int size) {
HashMap* map = (HashMap*)malloc(sizeof(HashMap));
map->size = size;
map->table = (HashNode**)malloc(size * sizeof(HashNode*)); // 确保分配的类型正确
for (int i = 0; i < size; i++) {
map->table[i] = NULL; // 初始化哈希表数组为空
}
return map;
}
7.哈希函数:
hashFunction
函数计算字符串的哈希值。
//定一个哈希表函数
int hashFunction(HashMap* map, char* key) {
int hash = 0;
while (*key)
{
hash = (hash * 31 + *key) % map->size;//计算哈希值
key++;
}
return hash;
}
8.插入哈希表:
hashMapInsert
函数将键值对插入哈希表。
//插入哈希表
void hashMapInsert(HashMap* map, char* key, int value) {
int hash = hashFunction(map, key);
HashNode* node = map->table[hash];
while (node) {
if (strcmp(node->key, key) == 0) { // 如果键已经存在,更新值
node->value = value;
return;
}
node = node->next;
}
// 创建新节点
node = (HashNode*)malloc(sizeof(HashNode));
node->key = strdup(key);
node->value = value;
node->next = map->table[hash];
map->table[hash] = node;
}
9.查找哈希表:
hashMapFind
函数在哈希表中查找键对应的值。
// 查找哈希表
int hashMapFind(HashMap* map, char* key) {
int hash = hashFunction(map, key);
HashNode* node = map->table[hash];
while (node) {
if (strcmp(node->key, key) == 0) { // 找到键,返回值
return node->value;
}
node = node->next;
}
return -1; // 键不存在,返回-1
}
10.释放哈希表:
freeHashMap
函数释放哈希表占用的内存。
// 释放哈希表
void freeHashMap(HashMap* map) {
for (int i = 0; i < map->size; i++) {
HashNode* node = map->table[i];
while (node) {
HashNode* temp = node;
node = node->next;
free(temp->key);
free(temp);
}
}
free(map->table);
free(map);
}
11.合并账户:
accountsMerge
函数合并账户列表,使用并查集和哈希表进行邮箱的合并和映射。
12.辅助函数:
createAccounts
函数用于创建和初始化账户数据。
// 合并账户
char*** accountsMerge(char*** accounts, int accountsSize, int* accountsColSize, int* returnSize, int** returnColumnSizes) {
UnionFind* uf = createUnionFind(accountsSize * 10); // 假设每个账户最多有10个邮箱
HashMap* emailToID = createHashMap(accountsSize * 10); // 邮箱到ID的映射
HashMap* emailToName = createHashMap(accountsSize * 10); // 邮箱到用户名的映射
int id = 0;
for (int i = 0; i < accountsSize; i++) {
char* name = accounts[i][0]; // 获取账户名称
for (int j = 1; j < accountsColSize[i]; j++) {
char* email = accounts[i][j]; // 获取邮箱
if (hashMapFind(emailToID, email) == -1) { // 如果邮箱未映射,添加映射
hashMapInsert(emailToID, email, id++);
hashMapInsert(emailToName, email, (int)name);
}
int emailID = hashMapFind(emailToID, email);
unionSets(uf, hashMapFind(emailToID, accounts[i][1]), emailID); // 合并集合
}
}
HashMap* components = createHashMap(id); // 创建组件哈希表
for (int i = 0; i < accountsSize; i++) {
for (int j = 1; j < accountsColSize[i]; j++) {
char* email = accounts[i][j];
int rootID = find(uf, hashMapFind(emailToID, email)); // 查找根节点ID
if (hashMapFind(components, (char*)rootID) == -1) {
hashMapInsert(components, (char*)rootID, rootID); // 插入组件
}
}
}
*returnSize = id;
*returnColumnSizes = (int*)malloc(id * sizeof(int)); // 初始化返回列大小数组
char*** result = (char***)malloc(id * sizeof(char**)); // 初始化结果数组
int* tempSizes = (int*)calloc(id, sizeof(int)); // 初始化临时大小数组
for (int i = 0; i < id; i++) {
result[i] = (char**)malloc(20 * sizeof(char*)); // 假设最多有20个邮箱
}
for (int i = 0; i < accountsSize; i++) {
for (int j = 1; j < accountsColSize[i]; j++) {
char* email = accounts[i][j];
int rootID = find(uf, hashMapFind(emailToID, email)); // 查找根节点ID
result[rootID][tempSizes[rootID]++] = email; // 添加邮箱到结果中
}
}
for (int i = 0; i < id; i++) {
qsort(result[i], tempSizes[i], sizeof(char*), (int(*)(const void*, const void*))strcmp); // 排序邮箱
char* name = (char*)hashMapFind(emailToName, result[i][0]); // 查找用户名
result[i][0] = name; // 将用户名添加到结果的第一个位置
(*returnColumnSizes)[i] = tempSizes[i]; // 设置每个账户的列大小
}
free(tempSizes); // 释放临时大小数组
freeUnionFind(uf); // 释放并查集
freeHashMap(emailToID); // 释放邮箱到ID的哈希表
freeHashMap(emailToName); // 释放邮箱到名称的哈希表
freeHashMap(components); // 释放组件哈希表
return result; // 返回合并后的账户数组
}
// 辅助函数:创建和初始化accounts数据
char*** createAccounts(int accountsSize, int* accountsColSize, char* values[][5]) {
char*** accounts = (char***)malloc(accountsSize * sizeof(char**));
for (int i = 0; i < accountsSize; i++) {
accounts[i] = (char**)malloc(accountsColSize[i] * sizeof(char*));
for (int j = 0; j < accountsColSize[i]; j++) {
accounts[i][j] = values[i][j]; // 初始化账户数组
}
}
return accounts;
}
完整的代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//定义并查集结构
typedef struct {
int* parent;//定义父节点数组
int* rank;//定义排名数组用于优化合并操作
}UnionFind;
//初始化并查集
UnionFind* createUnionFind(int size) {
UnionFind* uf = (UnionFind*)malloc(sizeof(UnionFind));
uf->parent = (int*)malloc(size * sizeof(int));
uf->rank = (int*)malloc(size * sizeof(int));
//将每个节点的父节点初始化为其自身,将每个节点的排名初始化为0
for (int i = 0; i < size; i++)
{
uf->parent[i] = i;
uf->rank[i] = 0;
}
return uf;
}
//定义一个函数用于查找根节点,带路径压缩优化
int find(UnionFind* uf, int x) {
if (uf->parent[x] != x)
{
uf->parent[x] = find(uf, uf->parent[x]);//路径压缩
}
}
//定义一个函数用于合并两个集合,并按秩合并优化
void unionSets(UnionFind* uf, int x, int y) {
int rootX = find(uf, x);
int rootY = find(uf, y);
if (rootX != rootY) {
if (uf->rank[rootX] > uf->rank[rootY])
{
uf->parent[rootY] = rootX;
}
else if (uf->rank[rootX] < uf->rank[rootY]) {
uf->parent[rootX] = rootY;
}
else
{
uf->parent[rootY] = rootX;
uf->parent[rootX]++;
}
}
}
//定义一个函数将并查集释放掉
void freeUnionFind(UnionFind* uf) {
free(uf->parent);
free(uf->rank);
free(uf);
}
//定义一个哈希表节点结构
typedef struct HashNode {
char* key;//哈希表的键
int value;//定义哈希表的值
struct HashNode* next;//定义链表中的下一个指针
};
//定义哈希表的结构
typedef struct {
int size;//定义哈希表的大小
HashNode** table;//定义指向哈希表数组的指针
}HashMap;
// 创建哈希表
HashMap* createHashMap(int size) {
HashMap* map = (HashMap*)malloc(sizeof(HashMap));
map->size = size;
map->table = (HashNode**)malloc(size * sizeof(HashNode*)); // 确保分配的类型正确
for (int i = 0; i < size; i++) {
map->table[i] = NULL; // 初始化哈希表数组为空
}
return map;
}
//定一个哈希表函数
int hashFunction(HashMap* map, char* key) {
int hash = 0;
while (*key)
{
hash = (hash * 31 + *key) % map->size;//计算哈希值
key++;
}
return hash;
}
//插入哈希表
void hashMapInsert(HashMap* map, char* key, int value) {
int hash = hashFunction(map, key);
HashNode* node = map->table[hash];
while (node) {
if (strcmp(node->key, key) == 0) { // 如果键已经存在,更新值
node->value = value;
return;
}
node = node->next;
}
// 创建新节点
node = (HashNode*)malloc(sizeof(HashNode));
node->key = strdup(key);
node->value = value;
node->next = map->table[hash];
map->table[hash] = node;
}
// 查找哈希表
int hashMapFind(HashMap* map, char* key) {
int hash = hashFunction(map, key);
HashNode* node = map->table[hash];
while (node) {
if (strcmp(node->key, key) == 0) { // 找到键,返回值
return node->value;
}
node = node->next;
}
return -1; // 键不存在,返回-1
}
// 释放哈希表
void freeHashMap(HashMap* map) {
for (int i = 0; i < map->size; i++) {
HashNode* node = map->table[i];
while (node) {
HashNode* temp = node;
node = node->next;
free(temp->key);
free(temp);
}
}
free(map->table);
free(map);
}
// 合并账户
char*** accountsMerge(char*** accounts, int accountsSize, int* accountsColSize, int* returnSize, int** returnColumnSizes) {
UnionFind* uf = createUnionFind(accountsSize * 10); // 假设每个账户最多有10个邮箱
HashMap* emailToID = createHashMap(accountsSize * 10); // 邮箱到ID的映射
HashMap* emailToName = createHashMap(accountsSize * 10); // 邮箱到用户名的映射
int id = 0;
for (int i = 0; i < accountsSize; i++) {
char* name = accounts[i][0]; // 获取账户名称
for (int j = 1; j < accountsColSize[i]; j++) {
char* email = accounts[i][j]; // 获取邮箱
if (hashMapFind(emailToID, email) == -1) { // 如果邮箱未映射,添加映射
hashMapInsert(emailToID, email, id++);
hashMapInsert(emailToName, email, (int)name);
}
int emailID = hashMapFind(emailToID, email);
unionSets(uf, hashMapFind(emailToID, accounts[i][1]), emailID); // 合并集合
}
}
HashMap* components = createHashMap(id); // 创建组件哈希表
for (int i = 0; i < accountsSize; i++) {
for (int j = 1; j < accountsColSize[i]; j++) {
char* email = accounts[i][j];
int rootID = find(uf, hashMapFind(emailToID, email)); // 查找根节点ID
if (hashMapFind(components, (char*)rootID) == -1) {
hashMapInsert(components, (char*)rootID, rootID); // 插入组件
}
}
}
*returnSize = id;
*returnColumnSizes = (int*)malloc(id * sizeof(int)); // 初始化返回列大小数组
char*** result = (char***)malloc(id * sizeof(char**)); // 初始化结果数组
int* tempSizes = (int*)calloc(id, sizeof(int)); // 初始化临时大小数组
for (int i = 0; i < id; i++) {
result[i] = (char**)malloc(20 * sizeof(char*)); // 假设最多有20个邮箱
}
for (int i = 0; i < accountsSize; i++) {
for (int j = 1; j < accountsColSize[i]; j++) {
char* email = accounts[i][j];
int rootID = find(uf, hashMapFind(emailToID, email)); // 查找根节点ID
result[rootID][tempSizes[rootID]++] = email; // 添加邮箱到结果中
}
}
for (int i = 0; i < id; i++) {
qsort(result[i], tempSizes[i], sizeof(char*), (int(*)(const void*, const void*))strcmp); // 排序邮箱
char* name = (char*)hashMapFind(emailToName, result[i][0]); // 查找用户名
result[i][0] = name; // 将用户名添加到结果的第一个位置
(*returnColumnSizes)[i] = tempSizes[i]; // 设置每个账户的列大小
}
free(tempSizes); // 释放临时大小数组
freeUnionFind(uf); // 释放并查集
freeHashMap(emailToID); // 释放邮箱到ID的哈希表
freeHashMap(emailToName); // 释放邮箱到名称的哈希表
freeHashMap(components); // 释放组件哈希表
return result; // 返回合并后的账户数组
}
// 辅助函数:创建和初始化accounts数据
char*** createAccounts(int accountsSize, int* accountsColSize, char* values[][5]) {
char*** accounts = (char***)malloc(accountsSize * sizeof(char**));
for (int i = 0; i < accountsSize; i++) {
accounts[i] = (char**)malloc(accountsColSize[i] * sizeof(char*));
for (int j = 0; j < accountsColSize[i]; j++) {
accounts[i][j] = values[i][j]; // 初始化账户数组
}
}
return accounts;
}
// 测试函数
int main() {
// 测试用例中的数组定义部分
int accountsSize1 = 4;
int accountsColSize1[] = { 3, 2, 3, 2 };
char* values1[4][5] = {
{"John", "[email protected]", "[email protected]", NULL, NULL},
{"John", "[email protected]", NULL, NULL, NULL},
{"John", "[email protected]", "[email protected]", NULL, NULL},
{"Mary", "[email protected]", NULL, NULL, NULL}
};
// 第二个测试用例
int accountsSize2 = 5;
int accountsColSize2[] = { 4, 4, 4, 4, 4 };
char* values2[5][5] = {
{"Gabe", "[email protected]", "[email protected]", "[email protected]", NULL},
{"Kevin", "[email protected]", "[email protected]", "[email protected]", NULL},
{"Ethan", "[email protected]", "[email protected]", "[email protected]", NULL},
{"Hanzo", "[email protected]", "[email protected]", "[email protected]", NULL},
{"Fern", "[email protected]", "[email protected]", "[email protected]", NULL}
};
// 创建账户数据
char*** accounts1 = createAccounts(accountsSize1, accountsColSize1, values1);
char*** accounts2 = createAccounts(accountsSize2, accountsColSize2, values2);
int returnSize1, returnSize2;
int* returnColumnSizes1;
int* returnColumnSizes2;
// 合并账户
char*** result1 = accountsMerge(accounts1, accountsSize1, accountsColSize1, &returnSize1, &returnColumnSizes1);
char*** result2 = accountsMerge(accounts2, accountsSize2, accountsColSize2, &returnSize2, &returnColumnSizes2);
// 打印结果
printf("测试用例 1:\n");
for (int i = 0; i < returnSize1; i++) {
printf("[");
for (int j = 0; j < returnColumnSizes1[i]; j++) {
printf("%s", result1[i][j]);
if (j < returnColumnSizes1[i] - 1) {
printf(", ");
}
}
printf("]\n");
}
printf("\n测试用例 2:\n");
for (int i = 0; i < returnSize2; i++) {
printf("[");
for (int j = 0; j < returnColumnSizes2[i]; j++) {
printf("%s", result2[i][j]);
if (j < returnColumnSizes2[i] - 1) {
printf(", ");
}
}
printf("]\n");
}
// 释放内存
free(accounts1);
free(accounts2);
free(result1);
free(result2);
free(returnColumnSizes1);
free(returnColumnSizes2);
return 0;
}