题目描述
一个公司准备组织一场会议,邀请名单上有 n 位员工。公司准备了一张 圆形 的桌子,可以坐下 任意数目 的员工。
员工编号为 0 到 n - 1 。每位员工都有一位 喜欢 的员工,每位员工 当且仅当 他被安排在喜欢员工的旁边,他才会参加会议。每位员工喜欢的员工 不会 是他自己。
给你一个下标从 0 开始的整数数组 favorite ,其中 favorite[i] 表示第 i 位员工喜欢的员工。请你返回参加会议的 最多员工数目 。
做题情况
- 做出来且思路与标答一致
- 做出来但思路较为复杂
- 有思路,但时间复杂度较高无法通过
- 没有思路 ☑
自己的想法:
可能因为自己的状态没有很好,所以对这道题没有什么想法。不过这种算法确实是自己第一次接触,很难全部想清楚。
这其实用到了一种新的数据结构,也不能说是数据结构吧,就是一种有名字的结构,而且这种结构和这道题完美的契合,这就蛮有趣的了。
标答:基环内向树+拓扑排序
首先介绍一下基环内向树。
顾名思义,一棵树有一个内环,而且是向内的,即向环的。这种结构就很像之前的用柳枝编成的花环。
这个结构与这道题完美契合:每个点有且仅有一个非自身的前驱节点,因此我们就根据这个指向来构造树,最终一定会走入到一个环中。(因为n个节点就会有n个前驱节点,且所有的前驱节点均为这n个点。我们沿着前驱节点的方向可以一直走,即一定会走入到一个环中去)。
可以预想到,所有的节点分成了很多堆,每一堆都有一个环。显然的,让一个环围着桌子坐显然是一种可行解。
但如果这个环只有两个节点,这两个节点相邻坐,就已经满足和喜欢的员工相邻的条件了。这种情况下,我们可以拓展枝叶,即从这个二元环向外延申,延申出的枝叶同样可以沿着桌子坐。
如何求延申出枝叶的最长长度,就需要用到拓扑排序。
我们用一个队列来表示当前没有前驱节点的节点,那么他的最长长度就是前驱节点中最长的长度。
(这个就不多讲了,感觉自己掌握的还不错)
还有一个细节,就是可以扩展多个只有两个点的环,包括环两个点延申出的枝叶。所以解分为两种,一种是多节点环,这个只能有一个环,一种是二元环,这个可以很多个二元环一起,包括枝叶。
实际代码
class Solution {
public:
int maximumInvitations(vector<int>& favorite)
{
//前面的排序可以用一个queue来进行实现
//剩下的就是在环上的,可以用一个set来实现,每次取第一个
int n = favorite.size();
vector<vector<int>> connect(n); //前驱节点
for (int i = 0; i < n; ++i) connect[favorite[i]].push_back(i);
vector<int> last_num(n); //变化量,前驱节点有几个
vector<int> length(n, -1); //前驱节点个数
queue<int> que;
for (int i = 0; i < n; ++i)
{
last_num[i] = connect[i].size();
if (last_num[i] == 0) que.push(i);
}
unordered_set<int> un_set;
for (int i = 0; i < n; ++i) un_set.insert(i);
while (!que.empty())
{
int top = que.front();
que.pop();
un_set.erase(top);
if (connect[top].size() == 0) length[top] = 1;
else
{
int len = 0;
for (int i = 0; i < connect[top].size(); ++i)
{
if (len < length[connect[top][i]]) len = length[connect[top][i]];
}
length[top] = len + 1;
}
last_num[favorite[top]]--;
if (last_num[favorite[top]] == 0) que.push(favorite[top]);
}
int res = 0;
int lennum2 = 0;
while (un_set.size())
{
auto iter = un_set.begin();
int first = *iter;
int it = favorite[first];
int len = 1;
un_set.erase(it);
while (it != first)
{
it = favorite[it];
un_set.erase(it);
len++;
}
if (len == 2)
{
int len1 = 0;
for (int i = 0; i < connect[first].size(); ++i)
{
if (length[connect[first][i]] > len1) len1 = length[connect[first][i]];
}
int len2 = 0;
for (int i = 0; i < connect[favorite[first]].size(); ++i)
{
if (length[connect[favorite[first]][i]] > len2) len2 = length[connect[favorite[first]][i]];
}
lennum2 += (2 + len1 + len2);
}
else
{
if (len > res) res = len;
}
}
return max(res, lennum2);
}
};
总结
这道题主要就是对于基环向内树的学习了。感觉这种数据结构,知道就是知道,不知道就是不知道,特征鲜明,应用场景比较局限。这次见到了,之后应该就不会不懂了。
希望之后可以灵活应用。