本文涉及知识点
图论 基环内向树
LeetCode2127. 参加会议的最多员工数
一个公司准备组织一场会议,邀请名单上有 n 位员工。公司准备了一张 圆形 的桌子,可以坐下 任意数目 的员工。
员工编号为 0 到 n - 1 。每位员工都有一位 喜欢 的员工,每位员工 当且仅当 他被安排在喜欢员工的旁边,他才会参加会议。每位员工喜欢的员工 不会 是他自己。
给你一个下标从 0 开始的整数数组 favorite ,其中 favorite[i] 表示第 i 位员工喜欢的员工。请你返回参加会议的 最多员工数目 。
示例 1:
输入:favorite = [2,2,1,2]
输出:3
解释:
上图展示了公司邀请员工 0,1 和 2 参加会议以及他们在圆桌上的座位。
没办法邀请所有员工参与会议,因为员工 2 没办法同时坐在 0,1 和 3 员工的旁边。
注意,公司也可以邀请员工 1,2 和 3 参加会议。
所以最多参加会议的员工数目为 3 。
示例 2:
输入:favorite = [1,2,0]
输出:3
解释:
每个员工都至少是另一个员工喜欢的员工。所以公司邀请他们所有人参加会议的前提是所有人都参加了会议。
座位安排同图 1 所示:
- 员工 0 坐在员工 2 和 1 之间。
- 员工 1 坐在员工 0 和 2 之间。
- 员工 2 坐在员工 1 和 0 之间。
参与会议的最多员工数目为 3 。
示例 3:
输入:favorite = [3,0,1,4,1]
输出:4
解释:
上图展示了公司可以邀请员工 0,1,3 和 4 参加会议以及他们在圆桌上的座位。
员工 2 无法参加,因为他喜欢的员工 1 旁边的座位已经被占领了。
所以公司只能不邀请员工 2 。
参加会议的最多员工数目为 4 。
提示:
n == favorite.length
2 <= n <= 105
0 <= favorite[i] <= n - 1
favorite[i] != i
基环内向树
边反向其临接表为:vNeiBo。反向后变成 基环外向树。
分三步:
一,拓扑排序,区分环点和非环点。
二,计算各环长度。
三,如果环的长度为2,还要看外向树最长的枝。
答案有两种情况:
一,一个长度大于3的环。
二,若干个长度2的环,环的两个端点各带最长的枝。
代码
核心代码
class CTopSort { public: template <class T = vector<int> > void Init(const vector<T>& vNeiBo,bool bDirect = true ) { const int iDelOutDeg = bDirect ? 0 : 1; m_c = vNeiBo.size(); m_vBackNeiBo.resize(m_c); vector<int> vOutDeg(m_c); for (int cur = 0; cur < m_c; cur++) { vOutDeg[cur] = vNeiBo[cur].size(); for (const auto& next : vNeiBo[cur]) { m_vBackNeiBo[next].emplace_back(cur); } } vector<bool> m_vHasDo(m_c); queue<int> que; for (int i = 0; i < m_c; i++) { if ( vOutDeg[i] <= iDelOutDeg) { m_vHasDo[i] = true; if (OnDo(i)) { que.emplace(i); } } } while (que.size()) { const int cur = que.front(); que.pop(); for (const auto& next : m_vBackNeiBo[cur]) { if (m_vHasDo[next] ) { continue; } vOutDeg[next]--; if (vOutDeg[next] <= iDelOutDeg ) { m_vHasDo[next] = true; if (OnDo(next)) { que.emplace(next); } } } }; } int m_c; protected: virtual bool OnDo( int cur) = 0; vector<vector<int>> m_vBackNeiBo; }; class CMyTopSort : public CTopSort { public: CMyTopSort(int iSize) { m_bCyc.assign(iSize,true); } vector<bool> m_bCyc; protected: virtual bool OnDo(int cur) override { m_bCyc[cur] = false; return true; } }; class Solution { public: int maximumInvitations(vector<int>& favorite) { m_c = favorite.size(); CMyTopSort topSort(m_c); vector<vector<int>> vNeiBo(m_c); for (int i = 0; i < m_c; i++) { vNeiBo[favorite[i]].emplace_back(i); } topSort.Init(vNeiBo); vector<bool> vVisit(m_c); int iMaxLenCyc = 0; int iCyc2 = 0; for (int i = 0; i < m_c; i++) { if ((!topSort.m_bCyc[i]) || vVisit[i]) { continue; } int len = 0; int next = i; do { vVisit[next] = true; next = favorite[next]; len++; } while (i != next); iMaxLenCyc = max(iMaxLenCyc, len); if (2 == len) { iCyc2 += DFS(i,vNeiBo, topSort) + DFS(favorite[i], vNeiBo, topSort); } } return max(iCyc2, iMaxLenCyc); } int DFS(int cur, vector<vector<int>>& vNeiBo, CMyTopSort& topSort) { int iMaxChildLen = 0; for (const auto& next : vNeiBo[cur]) { if (topSort.m_bCyc[next]) { continue; } iMaxChildLen = max(iMaxChildLen, DFS(next,vNeiBo,topSort)); } return iMaxChildLen + 1; } int m_c; };
测试用例
template<class T, class T2> void Assert(const T& t1, const T2& t2) { assert(t1 == t2); } template<class T> void Assert(const vector<T>& v1, const vector<T>& v2) { if (v1.size() != v2.size()) { assert(false); return; } for (int i = 0; i < v1.size(); i++) { Assert(v1[i], v2[i]); } } int main() { vector<int> favorite; { Solution sln; favorite = { 2, 2, 1, 2 }; auto res = sln.maximumInvitations(favorite); Assert(3, res); } { Solution sln; favorite = { 1,2,0 }; auto res = sln.maximumInvitations(favorite); Assert(3, res); } { Solution sln; favorite = { 3,0,1,4,1 }; auto res = sln.maximumInvitations(favorite); Assert(4, res); } }
2023年8月版
class Solution { public: int maximumInvitations(vector& favorite) { //如果A喜欢B,则A节点指向B,即每个节点都有且只有一条出边 //每个节点都有且只有一条出边=> 每联通分量边数等于节点数 //=>某个联通分量点边为m,从起点出发m次后,包括起点工访问m+1个节点,必定有一个节点重复 //=>即必定有环 //由于只有一出边,所以必定只有一个环,从进入环后,不会访问其它节点 //如果是节点大于3的环,则只能是整个环,不能多一个节点,也不能少一个节点。3节点的环,无法共存。 //如果是两个节点的环,则环+这两个节点,各选一枝。 多个2节点环可以共存。 m_c = favorite.size(); vector vIn(m_c);//入度 for (int i = 0; i < m_c; i++) { vIn[favorite[i]]++; } vector vLeve(m_c);//环上:0;不被任何人喜欢:1; 只被leve[1]的喜欢:2;只被leve[1] leve[2]喜欢:3 std::queue que; for (int i = 0; i < m_c; i++) { if (0 == vIn[i]) { vLeve[i] = 1; que.emplace(i); } } while (que.size()) { const int cur = que.front(); que.pop(); const int next = favorite[cur]; vIn[next]–; if (0 == vIn[next]) { que.emplace(next); vLeve[next] = vLeve[cur] + 1; } } vector vLen(m_c); for (int i = 0; i < m_c; i++) { const int next = favorite[i]; vLen[next] = max(vLen[next], vLeve[i]+1); } //其它必定是环 for (int i = 0; i < m_c; i++) { if (vLeve[i]) { continue; } int next = favorite[i]; int leve = 2; for (; favorite[next] != i; next = favorite[next]) { leve++; vLeve[next] = 10000; } vLeve[next] = 10000; m_iMaxCycle = max(m_iMaxCycle, leve); if (2 == leve) { m_iMaxCycle2 += vLen[i] + vLen[next]; } } return max(m_iMaxCycle, m_iMaxCycle2); } int m_c; int m_iMaxCycle = 0; int m_iMaxCycle2 = 0; };
扩展阅读
视频课程
有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
相关
下载
想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653
我想对大家说的话 |
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。