11

the problem goes like this

suggest a data structure and write a program to count the number of employees referred by an employee(directly or indirectly) in linear time. for example

  A B C D E F G 
A 0 1 0 0 0 0 0 A referred 4 (A referred B, B referred C and D and D referred E)
B 0 0 1 1 0 0 0 B referred 3 
C 0 0 0 0 0 0 0
D 0 0 0 0 1 0 0 D referred 1
E 0 0 0 0 0 0 0
F 0 0 0 0 0 0 1 F referred 1
G 0 0 0 0 0 0 0 

did this using a two dimensional array, can it be done in linear time?

Note that an employee can obviously not be recommended by someone he directly or indirectly recommended, so there will be no cycles in the graph. A new employee can be recommended by only one employee. while each employee can recommend multiple employees.

RiaD
  • 46,822
  • 11
  • 79
  • 123
Saqib
  • 685
  • 3
  • 15

6 Answers6

2

My solution using C#, I'm pretty sure this is less than N^2 but I'll need to look at it a little longer. Posting for critique while I do so.

public class Employee
{
    public List<Employee> Referred { get; set; }
    public string Name { get; set; }
    public int CountOfRefer { get; set; }
    public bool BeenCounted { get; set; }
    public Employee()
    {
        Referred = new List<Employee>();
    }
}

    public static void main()
    {
        Employee A = new Employee(){ Name="A" };
        Employee B = new Employee(){ Name="B" };
        Employee C = new Employee(){ Name="C" };
        Employee D = new Employee(){ Name="D" };
        Employee E = new Employee(){ Name="E" };
        Employee F = new Employee(){ Name="F" };
        Employee G = new Employee(){ Name="G" };

        A.Referred.Add(B);
        B.Referred.Add(C);
        B.Referred.Add(D);
        D.Referred.Add(E);
        F.Referred.Add(G);
        List<Employee> employees = new List<Employee>()
        {
            A, B, C, D, E, F, G
        };

        foreach (var emp in employees)
        {
            if (!emp.BeenCounted) countRefers(emp);
        }
    }

    private static int countRefers(Employee emp)
    {
        if (!emp.BeenCounted && emp.Referred.Count != 0)
        {
            foreach (var refs in emp.Referred)
            {
                emp.CountOfRefer += countRefers(refs);
            }
        }

        emp.BeenCounted = true;
        emp.CountOfRefer += emp.Referred.Count;
        return emp.CountOfRefer;
    }

Basically when counting an employee it recurses down the tree until it finds a person who has no refers (which should be guaranteed to be there, since people can't refer eachother (I guess unless there is only 1 person, ignoring that case for now)). Then if it has to calculate anybody through the recursion it sets a flag so it doesn't need to calculate it again.

Kevin DiTraglia
  • 25,746
  • 19
  • 92
  • 138
  • 1
    `foreach (var emp in employees) ... foreach (var refs in emp.Referred)` why yes, that _is_ O(N^2) (Optimized sure, so fast, but still O(N^2).) – Mooing Duck Jul 31 '13 at 20:16
  • Eh, yeah I guess you are right, seemed more clever as I was writing it. The first foreach doesn't have to execute any code if it gets iterated in the second loop, and the second loop doesn't execute if they have no refers. Whether I can prove that somehow isn't N^2 is unlikely though. – Kevin DiTraglia Jul 31 '13 at 20:17
  • Hold up, I'm rethinking this, the outer loop is you calculating the results for each employee I think, rather than just the first employee, and the inner loop/recursion has an _upper bound_ of the number of employees (or does it?), so even though it _appears_ O(N^2), I'm no longer sure it is.... – Mooing Duck Jul 31 '13 at 20:20
  • how is it O(N^2)? Each employee only has in it the employees it has referred. Say I have 5 employees, N^2 would mean that you would have to consider 25 items to move forward. This worst case scenario is 2N -1 aka n not n^2 where each employee refers the next employee. Am I wrong? – VoronoiPotato Jul 31 '13 at 20:20
  • @VoronoiPotato: I think `countRefers` gets called _up to_ 25 times for five employees (in the worst case). That'd be the easiest way to tell I think. – Mooing Duck Jul 31 '13 at 20:21
  • I'm ignoring the bugs in the code that only make the answer accurate for the first employee obviously – Mooing Duck Jul 31 '13 at 20:22
  • 1
    No wait, since the input cannot have loops, this does top out at `2N`(ish), so yeah, this is linear time. +1 – Mooing Duck Jul 31 '13 at 20:24
  • I counted it out, I got 11 times, I don't think it's n^2 – VoronoiPotato Jul 31 '13 at 20:24
  • Yeah the assumptions is that employees cannot refer eachother, and someone has to exist that didn't refer anyone. – Kevin DiTraglia Jul 31 '13 at 20:24
  • @VoronoiPotato With OPs test data it gets called 6 times (I put a static counter and writeline in there) – Kevin DiTraglia Jul 31 '13 at 20:27
  • yeah I was counting the inner and outer loops which sounds wrong. – VoronoiPotato Jul 31 '13 at 20:28
  • @VoronoiPotato Ah I see, in that case I get 13. – Kevin DiTraglia Jul 31 '13 at 20:30
  • @KevinDiTraglia: I just got further validation that each employee also has only one "referrer", which definitely simplifies the problem. – Mooing Duck Jul 31 '13 at 20:30
  • @MooingDuck Do you still think there's a bug in the code? Aside from infinitely recursing if there is a refer loop, it seems to work for any test input I give it. – Kevin DiTraglia Jul 31 '13 at 20:55
  • @KevinDiTraglia: I believe it only calculates the right answer for the first employee, maybe the second or third if you're lucky. You never clear BeenCounted ... wait... each person only has one referrer... Nope! Your code is fine! – Mooing Duck Jul 31 '13 at 21:13
  • I wrote two C++ versions of this code. [O(1) average with simple initialization](http://coliru.stacked-crooked.com/view?id=746c58a61edba588580fc1b72f3e6a4c-cc73e281b3b6abc9bf6cf4f153b944a6) and [O(1) worst case with two-phase initialization](http://coliru.stacked-crooked.com/view?id=32372ef0f87d66e2005846decf442e55-cc73e281b3b6abc9bf6cf4f153b944a6) (excluding the fact I return sets of referrers rather than merely the count, since I didn't want my counting algorithm to modify the data) – Mooing Duck Aug 02 '13 at 17:22
0

Graph method: first construct a directed graph (a tree in this case) where each node represents an employee and points to the node of the employee that referred them. This should take O(N^2) worst case ((N^2)/2 checks to be precise) where N is the number of employees. You should, as you're constructing this graph, keep track of the leaves of this tree (the employees that did not refer anyone) and then trim these nodes (assigning zero to their number of referrals) and adding 1 to the number of referrals of the employees that referred them. Then repeat with the new leaves, except add 2 to each of their referring nodes number of referrals. This whole trimming and counting process should take O(N) as well, and at the end all of the nodes contain the number of referrals each employee made.

This is assuming there are no cycles or weird two-employees-referring-the-same-employee situations in your graph (i.e. it is in fact a tree).

So, no, can't be done linearly if the input data to the problem is the binary vectors you have described. If we were given simply the names of the hired employees with each node, then O(N) would be possible.

MattG
  • 1,416
  • 2
  • 13
  • 31
0

You can build a graph first. This mustn't be included in the O(n) as it is clearly O(n^2)

It is not clear if you optimise the duplicate references (Imagine all the values are 1)

At this point you can start at one node and add all the referred nodes e.g. in a bit mask. Any values which were added you need to navigate recursively until you are adding no new nodes.

The number of nodes you visit is O(N) as each employee can refer one person.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
0

Java If cheating is allowed: an enum might represent the graph. Write first A(B), B(C, D), ... and then reorder so there is no forward reference on which the compiler complains. (This is always possible for non-cyclic references. Even a compile-time check against cyclic references.)

public class Refers {
    enum RefEnum {
        C(),
        E(),
        G(),
        D(E),
        F(G),
        B(C, D),
        A(B);

        //private final RefEnum[] refs;
        public final int refCount;

        private RefEnum(final RefEnum... refs) {
            //this.refs = refs;
            int n = 0;
            for (RefEnum ref : refs) {
                 n += 1 + ref.refCount;
            }
            refCount = n;
        }
    }

    public static void main(String[] args) {
        for (RefEnum ref : RefEnum.values()) {
            System.out.printf("%s has %d refers%n",
                    ref.toString(), ref.refCount);
        }
    }
}

The algorithm stays O(N²) however.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
0

This is a tree (more precisely a forest). Read the edges in O(n) time. Construct the tree in O(n.log(n)). Given a node, visit and count descendants in O(n).

necromancer
  • 23,916
  • 22
  • 68
  • 115
  • N-trees often have O(n.log(n)) operations, but this isn't a N-tree. I Can't figure out how you would construct the tree in O(n.log(n)), I can only figure out O(n) and O(n^2)... – Mooing Duck Aug 02 '13 at 16:54
0

You can maintain adjacency list for all the employees (N space). Then for each employee, maintain a Bag like data structure for all the referred employees. Then to count the references for Employee A, you can run DFS (Depth First Search) starting from A which is linear time algorithm.

Java code is here -

http://yogeshsp.blogspot.com/2013/04/graph-api.html

http://yogeshsp.blogspot.com/2013/05/depth-first-search-dfs-graph-processing.html

WastedPandoo
  • 153
  • 1
  • 10