【深度优先搜索】【树】【C++算法】2003. 每棵子树内缺失的最小基因值

简介: 【深度优先搜索】【树】【C++算法】2003. 每棵子树内缺失的最小基因值

本文涉及知识点

深度优先搜索

LeetCode2003. 每棵子树内缺失的最小基因值

有一棵根节点为 0 的 家族树 ,总共包含 n 个节点,节点编号为 0 到 n - 1 。给你一个下标从 0 开始的整数数组 parents ,其中 parents[i] 是节点 i 的父节点。由于节点 0 是 根 ,所以 parents[0] == -1 。

总共有 105 个基因值,每个基因值都用 闭区间 [1, 105] 中的一个整数表示。给你一个下标从 0 开始的整数数组 nums ,其中 nums[i] 是节点 i 的基因值,且基因值 互不相同 。

请你返回一个数组 ans ,长度为 n ,其中 ans[i] 是以节点 i 为根的子树内 缺失 的 最小 基因值。

节点 x 为根的 子树 包含节点 x 和它所有的 后代 节点。

示例 1:

输入:parents = [-1,0,0,2], nums = [1,2,3,4]

输出:[5,1,1,1]

解释:每个子树答案计算结果如下:

  • 0:子树包含节点 [0,1,2,3] ,基因值分别为 [1,2,3,4] 。5 是缺失的最小基因值。
  • 1:子树只包含节点 1 ,基因值为 2 。1 是缺失的最小基因值。
  • 2:子树包含节点 [2,3] ,基因值分别为 [3,4] 。1 是缺失的最小基因值。
  • 3:子树只包含节点 3 ,基因值为 4 。1是缺失的最小基因值。
    示例 2:

输入:parents = [-1,0,1,0,3,3], nums = [5,4,6,2,1,3]

输出:[7,1,1,4,2,1]

解释:每个子树答案计算结果如下:

  • 0:子树内包含节点 [0,1,2,3,4,5] ,基因值分别为 [5,4,6,2,1,3] 。7 是缺失的最小基因值。
  • 1:子树内包含节点 [1,2] ,基因值分别为 [4,6] 。 1 是缺失的最小基因值。
  • 2:子树内只包含节点 2 ,基因值为 6 。1 是缺失的最小基因值。
  • 3:子树内包含节点 [3,4,5] ,基因值分别为 [2,1,3] 。4 是缺失的最小基因值。
  • 4:子树内只包含节点 4 ,基因值为 1 。2 是缺失的最小基因值。
  • 5:子树内只包含节点 5 ,基因值为 3 。1 是缺失的最小基因值。
    示例 3:

输入:parents = [-1,2,3,0,2,4,1], nums = [2,3,4,5,6,7,8]

输出:[1,1,1,1,1,1,1]

解释:所有子树都缺失基因值 1 。

提示:

n == parents.length == nums.length

2 <= n <= 105

对于 i != 0 ,满足 0 <= parents[i] <= n - 1

parents[0] == -1

parents 表示一棵合法的树。

1 <= nums[i] <= 105

nums[i] 互不相同。

深度优先搜索

除了基因1的节点及它的祖先,其它节点都缺少1。

DFS(cur)结束时,处理了且只处理了它哥哥及自己的后代,如果我们将基因1及其祖先调整成长子。可以将空间复杂从O(nlogn)降低到O(n)。

注意:如果不优化,空间复杂度是O(nn),就是直接为每个节点分配空间,复制所有的后代。极端情况下,独子树的空间复杂度是O(nn)。直接用子树的空间,独子树空间复杂度O(n);非独子树O(nlong)。

超时代码

class CParentToNeiBo
{
public:
  CParentToNeiBo(const vector<int>& parents)
  {
    m_vNeiBo.resize(parents.size());
    for (int i = 0; i < parents.size(); i++)
    {
      if (-1 == parents[i])
      {
        m_root = i;
      }
      else
      {
        m_vNeiBo[parents[i]].emplace_back(i);
      }
    }
  }
  vector<vector<int>> m_vNeiBo;
  int m_root=-1;
};
class Solution {
public:
  vector<int> smallestMissingValueSubtree(vector<int>& parents, vector<int>& nums) {
    CParentToNeiBo neiBo(parents);
    m_nums = nums;
    m_vIs1.resize(nums.size());
    m_ans.assign(nums.size(),1);
    m_vHas.resize(100'000+10);
    DFS1(neiBo.m_root, neiBo.m_vNeiBo);
    for (auto& v : neiBo.m_vNeiBo)
    {
      for (int j = 1; j < v.size(); j++)
      {
        if (m_vIs1[v[j]])
        {
          std::swap(v[0], v[j]);
        }
      }
    }
    DFS2(neiBo.m_root, neiBo.m_vNeiBo);
    return m_ans;
  }
  void DFS2(int cur, vector<vector<int>>& neiBo)
  {   
    for (const auto& next : neiBo[cur])
    {
      DFS2(next, neiBo);
    }
    m_vHas[m_nums[cur]] = true;
    while (m_vHas[m_iNeed])
    {
      m_iNeed++;
    }
    if (m_vIs1[cur])
    {
      m_ans[cur] = m_iNeed;
    }
  }
  bool DFS1(int cur, vector<vector<int>>& neiBo)
  {
    bool b = (1 == m_nums[cur]);    
    for (const auto& next : neiBo[cur])
    {
      b |= DFS1(next, neiBo);
    }
    return m_vIs1[cur]=b;
  }
  vector<int> m_nums,m_ans;
  vector<bool> m_vIs1;
  int m_iNeed = 1;
  vector<bool> m_vHas;
};

1及其祖先不用DFS

class CParentToNeiBo
{
public:
  CParentToNeiBo(const vector<int>& parents)
  {
    m_vNeiBo.resize(parents.size());
    for (int i = 0; i < parents.size(); i++)
    {
      if (-1 == parents[i])
      {
        m_root = i;
      }
      else
      {
        m_vNeiBo[parents[i]].emplace_back(i);
      }
    }
  }
  vector<vector<int>> m_vNeiBo;
  int m_root=-1;
};
class Solution {
public:
  vector<int> smallestMissingValueSubtree(vector<int>& parents, vector<int>& nums) {
    CParentToNeiBo neiBo(parents);
    m_nums = nums;
    m_vIs1.resize(nums.size());
    m_ans.assign(nums.size(),1);
    m_vHas.resize(100'000+10);
    int i1 = std::find(nums.begin(), nums.end(), 1)- nums.begin();
    while ((-1 != i1) && (nums.size() != i1))
    {
      m_vIs1[i1] = true;
      i1 = parents[i1];
    }
    for (auto& v : neiBo.m_vNeiBo)
    {
      for (int j = 1; j < v.size(); j++)
      {
        if (m_vIs1[v[j]])
        {
          std::swap(v[0], v[j]);
        }
      }
    }
    DFS2(neiBo.m_root, neiBo.m_vNeiBo);
    return m_ans;
  }
  void DFS2(int cur, vector<vector<int>>& neiBo)
  {   
    for (const auto& next : neiBo[cur])
    {
      DFS2(next, neiBo);
    }
    m_vHas[m_nums[cur]] = true;   
    if (m_vIs1[cur])
    {
            while (m_vHas[m_iNeed])
    {
      m_iNeed++;
    }
      m_ans[cur] = m_iNeed;
    }
  }
  vector<int> m_nums,m_ans;
  vector<bool> m_vIs1;
  int m_iNeed = 1;
  vector<bool> m_vHas;
};

测试用例

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> parents,  nums;
  {
    Solution sln;
    parents = { -1, 0, 0, 2 }, nums = { 1, 2, 3, 4 };
    auto res = sln.smallestMissingValueSubtree(parents, nums);
    Assert({ 5,1,1,1 }, res);
  }
  
  {
    Solution sln;
    parents = { -1, 0, 1, 0, 3, 3 }, nums = { 5, 4, 6, 2, 1, 3 };
    auto res = sln.smallestMissingValueSubtree(parents, nums);
    Assert({ 7,1,1,4,2,1 }, res);
  }
  {
    Solution sln;
    parents = { -1, 2, 3, 0, 2, 4, 1 }, nums = { 2, 3, 4, 5, 6, 7, 8 };
    auto res = sln.smallestMissingValueSubtree(parents, nums);
    Assert({ 1,1,1,1,1,1,1 }, res);
  }
}

2023年2月版(当时能过)

class Solution {
public:
vector smallestMissingValueSubtree(const vector& parents, const vector& nums) {
m_c = nums.size();
m_vDirect.resize(m_c);
for (int i = 1; i < parents.size(); i++)
{
m_vDirect[parents[i]].push_back(i);
}
m_vVisiteValue.resize(m_c + 1);
m_vRet.assign(m_c, 1);
for (int i = 0; i < nums.size(); i++)
{
if (1 == nums[i])
{
DFS(i, -1,parents, nums);
break;
}
}
return m_vRet;
}
void DFS(int iCur, int iFromChild,const vector& parents, const vector& nums)
{
if (-1 == iCur)
{
return;
}
DFSForValue(iCur, iFromChild, nums);
int iMiss = (-1 == iFromChild) ? 1 : m_vRet[iFromChild];
while ((iMiss < m_vVisiteValue.size()) && (m_vVisiteValue[iMiss]))
{
iMiss++;
}
m_vRet[iCur] = iMiss;
DFS(parents[iCur], iCur, parents, nums);
}
void DFSForValue(int iCur, int iFromChild, const vector& nums)
{
m_vVisiteValue[nums[iCur]] = true;
for (auto& next : m_vDirect[iCur])
{
if (next == iFromChild)
{
continue;
}
DFSForValue(next, iFromChild, nums);
}
}
int m_c;
vector<vector> m_vDirect;
vector m_vRet;
vector m_vVisiteValue;
};


扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步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++**实现。

相关文章
|
1月前
|
算法
数据结构之路由表查找算法(深度优先搜索和宽度优先搜索)
在网络通信中,路由表用于指导数据包的传输路径。本文介绍了两种常用的路由表查找算法——深度优先算法(DFS)和宽度优先算法(BFS)。DFS使用栈实现,适合路径问题;BFS使用队列,保证找到最短路径。两者均能有效查找路由信息,但适用场景不同,需根据具体需求选择。文中还提供了这两种算法的核心代码及测试结果,验证了算法的有效性。
100 23
|
1月前
|
存储 C++
【C++】AVL树
AVL树是一种自平衡二叉搜索树,由Georgy Adelson-Velsky和Evgenii Landis提出。它通过确保任意节点的两子树高度差不超过1来维持平衡,支持高效插入、删除和查找操作,时间复杂度为O(log n)。AVL树通过四种旋转操作(左旋、右旋、左-右旋、右-左旋)来恢复树的平衡状态,适用于需要频繁进行数据操作的场景。
42 2
|
1月前
|
算法
树的遍历算法有哪些?
不同的遍历算法适用于不同的应用场景。深度优先搜索常用于搜索、路径查找等问题;广度优先搜索则在图的最短路径、层次相关的问题中较为常用;而二叉搜索树的遍历在数据排序、查找等方面有重要应用。
39 2
|
2月前
|
算法 Java 数据库
美团面试:百亿级分片,如何设计基因算法?
40岁老架构师尼恩分享分库分表的基因算法设计,涵盖分片键选择、水平拆分策略及基因法优化查询效率等内容,助力面试者应对大厂技术面试,提高架构设计能力。
美团面试:百亿级分片,如何设计基因算法?
|
2月前
|
算法 Java 数据库
美团面试:百亿级分片,如何设计基因算法?
40岁老架构师尼恩在读者群中分享了关于分库分表的基因算法设计,旨在帮助大家应对一线互联网企业的面试题。文章详细介绍了分库分表的背景、分片键的设计目标和建议,以及基因法的具体应用和优缺点。通过系统化的梳理,帮助读者提升架构、设计和开发水平,顺利通过面试。
美团面试:百亿级分片,如何设计基因算法?
|
2月前
|
存储 算法 C++
高精度算法(加、减、乘、除,使用c++实现)
高精度算法(加、减、乘、除,使用c++实现)
685 0
高精度算法(加、减、乘、除,使用c++实现)
|
2月前
|
存储 算法 关系型数据库
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
这篇文章主要介绍了多路查找树的基本概念,包括二叉树的局限性、多叉树的优化、B树及其变体(如2-3树、B+树、B*树)的特点和应用,旨在帮助读者理解这些数据结构在文件系统和数据库系统中的重要性和效率。
32 0
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
|
2月前
|
存储 算法
数据结构与算法学习十六:树的知识、二叉树、二叉树的遍历(前序、中序、后序、层次)、二叉树的查找(前序、中序、后序、层次)、二叉树的删除
这篇文章主要介绍了树和二叉树的基础知识,包括树的存储方式、二叉树的定义、遍历方法(前序、中序、后序、层次遍历),以及二叉树的查找和删除操作。
32 0
|
2月前
|
存储 算法 Java
数据结构和算法--分段树
数据结构和算法--分段树
25 0