【图论】【基环内向树】【广度优先】【深度优先】2127. 参加会议的最多员工数

简介: 【图论】【基环内向树】【广度优先】【深度优先】2127. 参加会议的最多员工数

本文涉及知识点

图论 基环内向树

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++**实现。

相关文章
|
3月前
|
Java C语言 C++
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-基础练习 圆的面积
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-基础练习 圆的面积
29 0
|
3月前
|
算法 Java C语言
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-8 算法训练 操作格子 线段树
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-8 算法训练 操作格子 线段树
29 0
|
4月前
|
算法 测试技术 C#
C++二分算法:最多可以参加的会议数目 II
C++二分算法:最多可以参加的会议数目 II
|
3月前
|
算法 Java C++
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-6 算法训练 安慰奶牛 最小生成树
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-6 算法训练 安慰奶牛 最小生成树
24 0
|
3月前
|
算法 Java C语言
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-4 算法训练 结点选择
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-4 算法训练 结点选择
43 0
|
4月前
|
算法 测试技术 C#
C++动态规划算法:最多可以参加的会议数目
C++动态规划算法:最多可以参加的会议数目
|
8月前
校门外的树(三种解法,非直接暴力)
校门外的树(三种解法,非直接暴力)
|
存储 算法
学会二叉树不知道干啥?二叉树的深度优先搜索和广度优先搜索,我要打十个乃至二十个(打开你的LeetCode撸起来)学练并举
学会二叉树不知道干啥?二叉树的深度优先搜索和广度优先搜索,我要打十个乃至二十个(打开你的LeetCode撸起来)学练并举
学会二叉树不知道干啥?二叉树的深度优先搜索和广度优先搜索,我要打十个乃至二十个(打开你的LeetCode撸起来)学练并举
|
存储 关系型数据库 索引
B+树层数计算(面试官直呼内行)
首先搞清楚一个常识,我们都知道计算机在存储数据的时候,有最小存储单元,这就好比我们今天进行现金的流通最小单位是一毛 在计算机中磁盘存储数据最小单元是扇区,一个扇区的大小是 512 字节,而文件系统(例如XFS/EXT4)他的最小单元是块,一个块的大小是 4k
1942 0
|
人工智能
一本通-加分二叉树+分离与合体(区间DP+记录方案)
一本通-加分二叉树+分离与合体(区间DP+记录方案)
120 0