【问题标题】:Algorithmic Puzzle: Distinct nodes in a subtree算法难题:子树中的不同节点
【发布时间】:2020-05-17 21:12:20
【问题描述】:

我正在尝试解决这个问题:

给定一棵由 n 个节点组成的有根树。节点是 编号为 1,2,…,n,节点 1 是根。每个节点都有一种颜色。

你的任务是为每个节点确定不同颜色的数量 在节点的子树中。

蛮力解决方案是为每个节点存储一个集合,然后在深度优先搜索中累积合并它们。那将在n^2 中运行,效率不高。

我如何有效地解决这个(以及同类问题)?

【问题讨论】:

  • 我不明白它是怎样的O(n^2)。如果树或多或少平衡,我们有T(n) = 2T(n/2) + O(n)(合并为O(n)),这意味着T(n) = O(n log n)
  • 树可能根本不平衡。我们对此一无所知。这在平衡树中是正确的,但是很容易创建复杂度大于 O(n log n) 的反例
  • 更具体地说,创建一个长度为 n / 2 的路径,为路径上的每个节点添加额外的子节点(叶) - 总共 n 个节点。合并那个需要 O(n^2) 时间。
  • cses.fi/problemset/task/1139 复制粘贴,不注明出处。我们要求您注明所有复制材料的来源:stackoverflow.com/help/referencing

标签: algorithm time-complexity


【解决方案1】:

对于每个节点,

  1. 递归遍历左右节点。
  2. 让每个调用返回一个颜色的 HashSet。
  3. 在每个节点,合并左子集,右子集。
  4. 更新 HashMap 中当前节点的计数。
  5. 添加当前节点的颜色并返回集合。

示例 C# 代码:

public Dictionary<Node, Integer> distinctColorCount = new ...

public HashSet<Color> GetUniqueColorsTill (TreeNode t) {
    // If null node, return empty set.
    if (t == null) return new HashSet<Color>();

    // If we reached here, we are at a non-null node.
    // First get the set from its left child.
    var lSet = GetUniqueColorsTill(t.Left);

    // Second get the set from its right child.
    var rSet = GetUniqueColorsTill(t.Right);

    // Now, merge the two sets.
    // Can be a little clever here. Merge smaller set to bigger set.
    var returnSet = rSet;
    returnSet.AddAll(lSet);

    // Put the count for this node in the dictionary.
    distinctColorCount[t] = returnSet.Count;    

    // Finally, add the color of current node and return.
    returnSet.Add(t.Color);

    return returnSet;
}

您可以完全按照@user58697 使用主定理对您的问题发表的评论来计算复杂性。 This 是我很久以前写的另一个答案,它解释了主定理,如果你需要复习的话。

【讨论】:

  • 如果树不平衡,这将在n^2 中运行。
  • @Maras:使用列表有两个问题。 1 - 保持排序,以“合并排序”方式合并它们。 2 - 删除重复项。使用哈希集可以为您解决这些问题。
  • @nz_21:每个节点都需要访问一次。如果树是倾斜的(假设没有树节点有右子节点),那么上面答案中的rSet 将为空,您将只有一个lSet,您将在每个节点处添加一种颜色。实际上,它将是 O(n),而不是 O(n^2)
  • 对,我没明白你没有复制更大的哈希集。实际上,这样每个值将被插入多达 log(n) 次。总复杂度确实预计为 O(n log n)。但是,您确实必须在您的孩子中选择最大的一组。如果您按照上面的说明执行伪代码(从左到右合并),那么您确实有 O(n^2) 最坏的情况。
  • 另外,它不一定是二叉树。鉴于此,我不确定渐近分析是否仍然成立
【解决方案2】:

首先,您需要将树更改为列表。这种技术通常被称为“Euler Tour”。

基本上,您创建一个空列表并运行 DFS。如果您第一次或最后一次访问节点,请将其颜色推到列表末尾。这样,您将获得长度为 2 * n 的列表,其中 n 等于节点数。很容易看出,在列表中,与节点的子节点对应的所有颜色都在其第一次和最后一次出现之间。现在,不是树和查询“节点的子树中有多少种不同的颜色”,而是列表并查询“索引 i-th 和 j-th 之间有多少种不同的颜色”。这实际上使事情变得容易得多。

第一个想法——MO的技术O(n sqrt(n)):

我将简要描述一下,我强烈建议搜索 MO 的技术,它在许多资料中都有很好的解释。

按开头对所有查询进行排序(其余的,它们看起来像这样:给定对 (i, j) 查找子数组中从索引 i 到索引 j 的所有不同数字)。制作 sqrt(n) 个桶,将查询从索引 i 开始放置到桶号 i / sqrt(n)。

对于每个存储桶,我们将分别回答查询。按末尾对存储桶中的所有查询进行排序。现在开始使用蛮力处理第一个(最左边的查询)(迭代子数组,将数字存储在 set/hashset/map/whatever 中,获取集合的大小)。

现在要处理下一个,我们将在末尾添加一些数字(下一个查询比前一个更远!),不幸的是,对它的开始做一些事情。我们需要从集合中删除一些数字(如果下一个查询的开始 > 旧查询开始)或从开头添加一些数字(如果下一个查询的开始

第二个想法——检查一下,O(n log n):Is it possible to query number of distinct integers in a range in O(lg N)?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-12
    • 2021-07-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多