547 朋友圈
题目描述: 班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。
给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
思路: 并查集。 并查集包括与节点大小相等的pre[]数组,find()函数,join()函数三部分。给出的矩阵为对称的邻接矩阵,如果值为1,则用join()函数将对应的两个点相连。find()函数中包含了路径压缩过程。注意路径压缩后,顶点并不一定是该连通域所用节点的前一节点。
class Solution {
public:
vector<int> pre;
int find(int x) {
return (pre[x] == x ? x : pre[x] = find(pre[x]));
}
void join(int x, int y) {
int prex = find(x);
int prey = find(y);
if(prex != prey)
pre[prex] = prey;
return ;
}
int findCircleNum(vector<vector<int>>& M) {
int N = M.size();
int ans = 0;
for(int i = 0; i < N; i++) {
pre.push_back(i);
}
for(int i = 0; i < N; i++) {
for(int j = i + 1; j < N; j++) {
if(M[i][j] == 1)
join(i, j);
}
}
for(int i = 0; i < N; i++) {
if(pre[i] == i)
ans ++;
}
return ans;
}
};
684 冗余连接
题目描述: 在本问题中, 树指的是一个连通且无环的无向图。
输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, …, N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。结果图是一个以边组成的二维数组。每一个边的元素是一对[u, v] ,满足 u < v,表示连接顶点u 和v的无向图的边。
返回一条可以删去的边,使得结果图是一个有着N个节点的树。如果有多个答案,则返回二维数组中最后出现的边。答案边 [u, v] 应满足相同的格式 u < v。
思路: 根据题意,在树中添加附加边后,图中将会出现一个环。环中的任意两个节点之间都有两条路径可达,即环中相邻两个顶点之间的边为该图中的冗余边,即所求答案。题目要求返回二维数组中最后出现的边,即按照给定边的顺序,使图中开始出现环的第一条边。并查集具体步骤同上一题目。
class Solution {
public:
vector<int> pre;
int find(int x) {
return (pre[x] == x ? x : pre[x] = find(pre[x]));
}
void join(int x, int y) {
if(find(x) != find(y))
pre[find(x)] = find(y);
return ;
}
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
int n = edges.size();
pre.push_back(0);
for(int i = 1; i <= n; i++) {
pre.push_back(i);
}
for(int i = 0; i < edges.size(); i++) {
if(find(edges[i][0]) == find(edges[i][1]))
return edges[i];
join(edges[i][0], edges[i][1]);
}
return {};
}
};
721 账户合并
题目描述: 给定一个列表 accounts,每个元素 accounts[i]
是一个字符串列表,其中第一个元素 accounts[i][0]
是 名称 (name),其余元素是 emails 表示该帐户的邮箱地址。
现在,我们想合并这些帐户。如果两个帐户都有一些共同的邮件地址,则两个帐户必定属于同一个人。请注意,即使两个帐户具有相同的名称,它们也可能属于不同的人,因为人们可能具有相同的名称。一个人最初可以拥有任意数量的帐户,但其所有帐户都具有相同的名称。
合并帐户后,按以下格式返回帐户:每个帐户的第一个元素是名称,其余元素是按顺序排列的邮箱地址。accounts 本身可以以任意顺序返回。
思路: 并查集。 将列表中的所有元素从0开始标号,将具有相同邮箱地址的两个元素相连。用unordered_map
记录每个邮箱属于列表中哪个元素,查找时,如果邮箱对应的value值不为零,则将该邮箱对应的编号与value值对应的编号相连(join()函数)。最后对pre[]
数组查找处理。
class Solution {
public:
// 并查集
vector<int> pre;
int find(int x) {
return (pre[x] == x ? x : pre[x] = find(pre[x]));
}
void join(int x, int y) {
if(find(x) != find(y))
pre[find(x)] = find(y);
return ;
}
static bool cmp(string a, string b) {
return a > b;
}
vector<vector<string>> accountsMerge(vector<vector<string>>& accounts) {
int n = accounts.size();
for(int i = 0; i < n; i++)
pre.push_back(i);
unordered_map<string, int> mp;
for(int i = 0; i < accounts.size(); i++)
for(int j = 1; j < accounts[i].size(); j++) {
if(mp[accounts[i][j]] != 0)
join(i, mp[accounts[i][j]] - 1);
mp[accounts[i][j]] = i + 1;
}
unordered_map<string, int> mp1;
vector<vector<string>> ans;
for(int i = 0; i < pre.size(); i++) {
if(pre[i] == i) { // i为顶点
vector<string> tmp;
for(int j = 0; j < pre.size(); j++)
if(find(j) == i) // 所有与i相连的点
for(int k = 1; k < accounts[j].size(); k++)
if(mp1[accounts[j][k]] == 0) {
tmp.push_back(accounts[j][k]);
mp1[accounts[j][k]] = 1;
}
sort(tmp.begin(), tmp.end(), cmp);
tmp.push_back(accounts[i][0]);
reverse(tmp.begin(), tmp.end());
ans.push_back(tmp);
}
}
return ans;
}
};
1319 连通网络的操作次数
题目描述: 用以太网线缆将 n 台计算机连接成一个网络,计算机的编号从 0 到 n-1。线缆用 connections 表示,其中 connections[i] = [a, b] 连接了计算机 a 和 b。
网络中的任何一台计算机都可以通过网络直接或者间接访问同一个网络中其他任意一台计算机。
给你这个计算机网络的初始布线 connections,你可以拔开任意两台直连计算机之间的线缆,并用它连接一对未直连的计算机。请你计算并返回使所有计算机都连通所需的最少操作次数。如果不可能,则返回 -1 。
思路: 并查集 本题目可以转化为找出冗余连接(684题)。统计出冗余连接的边数num1
,以及独立连通域的个数num2
,并进行比较。如果num1
小于num2 - 1
,则说明可以拔开的电缆不够用,返回-1,否则返回num2 - 1
。
class Solution {
public:
vector<int> pre;
int find(int x) {
return (pre[x] == x ? x : pre[x] = find(pre[x]));
}
void join(int x, int y) {
if(find(x) != find(y))
pre[find(x)] = find(y);
return ;
}
int makeConnected(int n, vector<vector<int>>& connections) {
for(int i = 0; i < n; i++)
pre.push_back(i);
int num1 = 0; // 冗余连接数
int num2 = 0; // 独立连通域数目
for(int i = 0; i < connections.size(); i++) {
if(find(connections[i][0]) == find(connections[i][1]))
num1++;
else
join(connections[i][0], connections[i][1]);
}
for(int i = 0; i < n; i++) {
if(pre[i] == i)
num2++;
}
if(num1 < num2 - 1)
return -1;
return num2 - 1;
}
};
399 除法求值
题目描述: 给出方程式 A / B = k, 其中 A 和 B 均为代表字符串的变量, k 是一个浮点型数字。根据已知方程式求解问题,并返回计算结果。如果结果不存在,则返回 -1.0。
示例:
给定 a / b = 2.0, b / c = 3.0
问题: a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ?
返回 [ 6.0, 0.5, -1.0, 1.0, -1.0 ]
基于上述例子,输入如下:
equations(方程式) = [ [“a”, “b”], [“b”, “c”] ],
values(方程式结果) = [2.0, 3.0],
queries(问题方程式) = [ [“a”, “c”], [“b”, “a”], [“a”, “e”], [“a”, “a”], [“x”, “x”] ].
思路: DFS(BFS)/并查集。
BFS: 构造一个双向图,比如a/b=2.0,那么a->b权重2.0,b->a权重0.5,要查的时候就用BFS遍历,每到一个新节点,就更新权重。
并查集: 带权重的并查集。 不会…o(╥﹏╥)o
class Solution {
// BFS
public:
unordered_map<string, vector<pair<string, double>>> mp; // 双向图
unordered_map<string, double> node_val; // 节点权重 值为:连通域中顶点/该节点的值
unordered_map<string, int> belong; // 该点属于哪个连通域
void bfs(string a, int cnt_belong) {
queue<string> Q;
Q.push(a); // 该点为此连通域中的顶点,所有此连通域中的点的val都以该点的权重为参照
belong[a] = cnt_belong;
node_val[a] = 1.0;
while(!Q.empty()) {
auto q = Q.front();
Q.pop();
// auto遍历数组
for(auto &pr : mp[q]) {
if(belong[pr.first] == 0) {
belong[pr.first] = cnt_belong;
node_val[pr.first] = node_val[q] / pr.second;
Q.push(pr.first);
}
}
}
return ;
}
vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
int n = equations.size();
for(int i = 0; i < n; i++) {
mp[equations[i][0]].push_back(make_pair(equations[i][1], values[i]));
mp[equations[i][1]].push_back(make_pair(equations[i][0], 1.0 / values[i]));
}
int cnt_belong = 1;
// auto遍历map
for(auto &pr : mp) {
if(belong[pr.first] == 0) {
bfs(pr.first, cnt_belong);
cnt_belong++;
}
}
vector<double> ans;
for(int i = 0; i < queries.size(); i++) {
if(belong[queries[i][0]] == 0 || belong[queries[i][1]] == 0 || belong[queries[i][0]] != belong[queries[i][1]]) {
ans.push_back(-1.0);
continue;
}
ans.push_back(node_val[queries[i][0]] / node_val[queries[i][1]]);
}
return ans;
}
};