树中的类似对

时间:2013-05-12 07:42:21

标签: algorithm search tree

这是Hackerrank比赛的一个问题:

您将获得一棵树,其中每个节点都标记为1,2,...,n。这棵树中有多少个相似的对(S)?

一对(A,B)是类似的对iff

  • 节点A是节点B的祖先
  • abs(A-B)< = T。

输入格式: 输入的第一行包含两个整数n和T.接着是n-1行,每行包含两个整数si和ei,其中节点si是节点ei的父节点。

输出格式: 输出一个整数,表示树中相似对的数量

约束:

1 <= n <= 100000  
0 <= T <= n  
1 <= si, ei <= n.  

也保证没有循环,但树不必是二叉树。

示例输入:

5 2
3 2
3 1
1 4
1 5

示例输出:

4

说明: 类似的对是:(3,2)(3,1)(3,4)(3,5)

现在,蛮力方法解决了大约一半的测试用例,但对于另一半,它只是缓慢。我试图通过存储节点子树的间隔来扩展算法,从而能够消除一些分支,但总体而言只需要几个点。

有关如何有效解决此搜索的任何想法?

4 个答案:

答案 0 :(得分:4)

嗯,这个解决方案怎么样?

按预先的顺序遍历树(如DFS),并维护一个多集S进行查询。

在输入节点x时,只需将x添加到S即可。在离开节点x时(在此上下文中,我指的是访问x的所有孩子之后的时间),请从x移除S。通过这样做,在树遍历期间的所有时间,{strong} x S 的所有祖先。

现在让我们计算一个元素为x的类似对。另一个元素(比如y)必须位于S(因为它必须是x的祖先),并且必须保留x - T <= y <= x + T。有多少这样的y?是的,您只需查询S 计算S 之间的[x-T, x+T]值中的元素数量。此查询可以在O(log N)时间内回答,因为S中的元素数量永远不会超过N.

更具体地,该数据结构的候选者是BST,或支持添加和删除操作的其他类似的树数据结构(例如,AVL树,RB树,Treap等等)。或者,Fenwick TreeSegment Tree也可以在O(log N)时间内进行这些查询。

总之,通过维护当前访问节点的所有祖先,并总结对的数量(包括当前节点),您可以找到所有类似对的数量。由于我们对树中的每个节点都有单一查询,因此整体时间复杂度为 O(N log N)

答案 1 :(得分:1)

我会尝试深度优先搜索树,使用二进制索引树(请参阅Topcoder tutorial)来存储堆栈中看到的所有值。

这允许您对所需范围内的父节点数进行O(log(n))查询,因此整体复杂度将为O(nlog(n))

答案 2 :(得分:1)

我用地图做了。您只需保存每个节点的父节点,然后递归计算每个节点的类似对。

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Solution
{

    static Map<Integer, Integer> map = new HashMap<Integer, Integer>();
    static int similarPairs = 0;

    public static void main(String[] args)
    {
        Scanner sc = new Scanner(System.in);

        int totalRows = sc.nextInt();
        int T = sc.nextInt();

        for (int i = 0; i < totalRows - 1; i++)
        {
            int data = sc.nextInt();
            int cdata = sc.nextInt();
            int currValue = cdata;

            map.put(cdata, data);

            while (map.get(cdata) != null)
            {

                int parent = map.get(cdata);

                if (Math.abs(parent - currValue) <= T)
                {
                    similarPairs += 1;
                }

                cdata = parent;
            }
        }

        System.out.println(similarPairs);
    }
}

答案 3 :(得分:0)

如下所示,可以在低级别C中实现,而无需任何列表/字典/循环结构,如哈希表。

  1. 读取输入数据,存储每个已解析的行
  2. 分配int[] arr_of_ints = new int[n*M]的整数数组,并将所有条目初始化为0.想法是节点i(其中,节点可以有的最大子节点数M) 1&lt; = i&lt; = n)对应于M个整数arr_of_ints [M *(i-1)]到arr_of_ints [M * i],并且这些条目的值是该节点的子节点
  3. 运行已保存的已解析数据,并填写arr_of_ints,确定哪个节点位于顶部
  4. 分配一个数组int[] ancestors = new int[n],该数组将存储所有祖先的值
  5. 最后,从树的顶部开始,使用数组ancestors向下运行树,使用数组ancestors,这样在每个节点上都没有必要回溯树来计算数量类似的节点。你的递归越深,你在数组class Solution { static char[] space = new char[] { ' ' }; class FileRow { public readonly int parent; public readonly int child; public FileRow(string str) { string[] split = str.Split(space); parent = int.Parse(split[0]); child = int.Parse(split[1]); } } public static void Main(String[] args) { List<FileRow> inputdata = new List<FileRow>(); StreamReader sr = File.OpenText("inputfile.txt"); String[] split = sr.ReadLine().Split(space); int n = int.Parse(split[0]), T = int.Parse(split[1]); int[] arr = new int[n]; // this will record the max num of children while (!sr.EndOfStream) { FileRow fr = new FileRow(sr.ReadLine()); arr[fr.parent - 1]++; inputdata.Add(fr); } sr.Close(); int M = 0; for (int i = 0; i < arr.Length; i++) { M = Math.Max(M, arr[i]); arr[i] = 0; // set all arr to zero, for use below in setting up tree } int[,] tree = new int[n, M]; Boolean[] not_root_node = new bool[n]; // all entries automatically initialised to false foreach (FileRow fr in inputdata) { not_root_node[fr.child - 1] = true; // indicate that node fr.child isn't the root node of the tree tree[fr.parent - 1, arr[fr.parent - 1]++] = fr.child; } int count = 0, node = 0; for (int i = 0; i < not_root_node.Length; i++) { if (!not_root_node[i]) { count++; node = i + 1; } arr[i] = 0; // set all arr to zero, for use below in calculating result } if (count != 1) throw new Exception("ERROR, root node for tree not unique, count="+count.ToString()); // int node is the root of the tree int level = 0, result = 0; int[] ancestors = new int[n]; do { int j = arr[node - 1]; int nextnode = (j < M ? tree[node - 1,j] : 0); if (nextnode == 0) { level--; if (level >=0) node = ancestors[level]; } else { ancestors[level++] = node; arr[node - 1]++; node = nextnode; for (int i = 0; i < level; i++) { if (Math.Abs(ancestors[i] - node) <= T) result++; } } } while (level >= 0); System.Diagnostics.Trace.WriteLine(result); } } 越远,但是当你到达分支的末端时,那个位置就会展开
据我所知,如果arr_of_ints [M * i]有足够的内存,这应该和它一样快。

更新:我已用C#编写了我的解决方案版本,见下文,我认为它是O(n log n)。

{{1}}