Bootstrap

力扣刷题之721.账户合并

题干描述:

给定一个列表 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.数据结构选择:

  1. 使用并查集来管理邮箱的合并操作。
  2. 使用哈希表来建立邮箱到ID以及邮箱到用户名的映射。

 2.初始化并查集和哈希表

  1. 初始化并查集,用于处理邮箱之间的合并操作。
  2. 初始化哈希表,用于邮箱到ID的映射以及邮箱到用户名的映射。

3.处理输入数据:

  1. 遍历输入的账户列表,对每个账户的每个邮箱进行处理。
  2. 为每个邮箱分配一个唯一的ID,并将该邮箱与其所属账户的第一个邮箱进行合并操作。 

4.建立邮箱与其根节点的映射:

  1. 再次遍历所有邮箱,通过并查集找到每个邮箱的根节点,将同一个根节点的邮箱合并在一起。

5.构建最终结果

  1. 根据每个根节点对应的邮箱列表,构建最终的结果列表,并进行排序。 

6.释放内存

  1. 释放并查集和哈希表占用的内存。 

 解题思路:

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.哈希表节点和结构定义:

    HashNodeHashMap 用于定义哈希表节点和哈希表结构。

//定义一个函数将并查集释放掉
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;
}

;