Post

Topological Sorting

Fundamentals

Topological sorting: In computer science, a topological sort or topological ordering of a directed graph is a linear ordering of its vertices such that for every directed edge uv from vertex u to vertex v, u comes before v in the ordering.

Kahn’s Algorithm

Directed Graph

Course Schedule II

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Kahn's algorithm
public int[] findOrder(int numCourses, int[][] prerequisites) {
    List<Integer>[] graph = new List[numCourses];
    for (int i = 0; i < numCourses; i++) {
        graph[i] = new ArrayList<>();
    }

    int[] indegrees = new int[numCourses];
    for (int[] p : prerequisites) {
        graph[p[1]].add(p[0]);
        indegrees[p[0]]++;
    }

    // zero indegree
    Queue<Integer> q = new LinkedList<>();
    for (int i = 0; i < numCourses; i++) {
        if (indegrees[i] == 0) {
            q.offer(i);
        }
    }

    int[] order = new int[numCourses];
    int count = 0;
    while (!q.isEmpty()) {
        int course = q.poll();
        for (int neighbor : graph[course]) {
            if (--indegrees[neighbor] == 0) {
                q.offer(neighbor);
            }
        }
        order[count++] = course;
    }

    return count == numCourses ? order : new int[0];
}

Find All Possible Recipes from Given Supplies

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public List<String> findAllRecipes(String[] recipes, List<List<String>> ingredients, String[] supplies) {
    int n = recipes.length;
    // {ingredient : recipe index}
    Map<String, List<Integer>> graph = new HashMap<>();
    int[] indegree = new int[n];
    Set<String> set = Arrays.stream(supplies).collect(Collectors.toSet());
    for (int i = 0; i < n; i++) {
        // if an ingredient is not in supplies, makes it a graph node
        // i.e. supplied ingredients are omitted in the graph
        for (String in : ingredients.get(i)) {
            if (!set.contains(in)) {
                graph.computeIfAbsent(in, k -> new ArrayList<>()).add(i);
                // indegrees[i] denotes the number of unsupplied ingredients of the i-th recipe
                indegrees[i]++;
            }
        }
    }

    // leaves are recipes with 0 indegree
    // i.e. whose ingredients are all supplied
    Queue<String> q = new LinkedList<>();
    for (int i = 0; i < n; i++) {
        if (indegrees[i] == 0) {
            q.offer(recipes[i]);
        }
    }

    List<String> list = new ArrayList<>();
    while (!q.isEmpty()) {
        String node = q.poll();
        if (graph.containsKey(node)) {
            for (int i : graph.get(node)) {
                if (--indegrees[i] == 0) {
                    q.offer(recipes[i]);
                }
            }
        }
        list.add(node);
    }
    return list;
}

Undirected Graph

For undirected graphs, leaves are nodes with degrees == 1.

Distance to a Cycle in Undirected Graph

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public int[] distanceToCycle(int n, int[][] edges) {
    int[] degrees = new int[n];
    List<Integer>[] graph = new List[n];
    for (int i = 0; i < n; i++) {
        graph[i] = new ArrayList<>();
    }
    for (int[] e : edges) {
        graph[e[0]].add(e[1]);
        graph[e[1]].add(e[0]);
        degrees[e[0]]++;
        degrees[e[1]]++;
    }

    // Enqueues leaves
    Queue<Integer> q = new LinkedList<>();
    // flags means "visited"
    boolean[] flags = new boolean[n];
    for (int i = 0; i < n; i++) {
        // Undirected graph leaf
        if (degrees[i] == 1) {
            q.offer(i);
            flags[i] = true;
        }
    }

    while (!q.isEmpty()) {
        int node = q.poll();
        for (int neighbor : graph[node]) {
            if (flags[neighbor]) {
                continue;
            }
            // Undirected graph
            if (--degrees[neighbor] == 1) {
                q.offer(neighbor);
                flags[neighbor] = true;
            }
        }
    }

    // DFS from the cycle to outer
    // Now flags means "unvisited"
    q.clear();
    for (int i = 0; i < n; i++) {
        if (!flags[i]) {
            q.offer(i);
        }
    }

    int[] answer = new int[n];
    int level = 1;
    while (!q.isEmpty()) {
        for (int i = q.size(); i > 0; i--) {
            int node = q.poll();
            for (int neighbor : graph[node]) {
                if (!flags[neighbor]) {
                    q.offer(neighbor);
                    answer[neighbor] = level;
                    flags[neighbor] = true;
                }
            }
        }
        level++;
    }
    return answer;
}

Path Length

Longest path in a DAG can be solved by topological sorting.

Longest Increasing Path in a Matrix

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// pads the grid with zero as boundaries
int[][] matrix = new int[m + 2][n + 2];
for (int i = 0; i < m; i++) {
    System.arraycopy(grid[i], 0, matrix[i + 1], 1, n);
}

int[][] outdegree = new int[m + 2][n + 2];
for (int i = 1; i <= m; i++) {
    for (int j = 1; j <= n; j++) {
        for (int[] d: dir) {
            if (matrix[i][j] < matrix[i + d[0]][j + d[1]]) {
                outdegree[i][j]++;
            }
        }
    }
}

Another solution is DFS + memoization

Number of Orderings

Number of topological orderings of a directed tree:

Count Ways to Build Rooms in an Ant Colony

\[\frac{n!}{\prod{s_i}}\]

where \(s_i\) is the size of the subtree at the i-th node.

DAG

A topological ordering is possible iff the graph has no directed cycles, that is, iff it is a directed acyclic graph (DAG).

Uniqueness

Uniqueness

If a topological sort has the property that all pairs of consecutive vertices in the sorted order are connected by edges, then these edges form a directed Hamiltonian path in the DAG.

A Hamiltonian path (or traceable path) is a path in an undirected or directed graph that visits each vertex exactly once.

Iff a Hamiltonian path exists, the topological sort order is unique; no other order respects the edges of the path.

If a topological sort does not form a Hamiltonian path, it is always possible to form a second valid ordering by swapping two consecutive vertices that are not connected by an edge to each other.

Hamilton Path

Sequence Reconstruction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public boolean sequenceReconstruction(int[] nums, List<List<Integer>> sequences) {
    int n = nums.length;
    // index[i]: index of element nums[i] in nums
    int[] index = new int[n + 1];
    for (int i = 0; i < n; i++) {
        index[nums[i]] = i;
    }

    // pairs[i]: nums[i] and nums[i + 1] make a pair
    boolean[] pairs = new boolean[n];
    for (List<Integer> seq : sequences) {
        for (int i = 0; i < seq.size(); i++) {
            if (seq.get(i) > n) {
                return false;
            }

            // each seq in sequences should be a subsequence of nums
            if (i > 0 && index[seq.get(i - 1)] >= index[seq.get(i)]) {
                return false;
            }

            // all pairs of consecutive elements in nums should be in some seq in sequences
            if (i > 0 && index[seq.get(i - 1)] + 1 == index[seq.get(i)]) {
                pairs[index[seq.get(i - 1)]] = true;
            }
        }
    }

    for (int i = 0; i < n - 1; i++) {
        if (!pairs[i]) {
            return false;
        }
    }

    return true;
}

A more intuitive solution is to reconstruct the topological sort from sequences and check if it’s unique and equal to nums.

Two-level Topological Sort

Sort Items by Groups Respecting Dependencies

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
private List<Integer>[] groupGraph, itemGraph;
private int[] groupsIndegree, itemsIndegree;

public int[] sortItems(int n, int m, int[] group, List<List<Integer>> beforeItems) {
    // maps items with -1 group to new isolated groups
    // and updates the group count
    for (int i = 0; i < n; i++) {
        if (group[i] < 0) {
            group[i] = m++;
        }
    }

    this.itemGraph = new ArrayList[n];
    this.groupGraph = new ArrayList[m];

    for (int i = 0; i < n; i++) {
        itemGraph[i] = new ArrayList<>();
    }
    for (int i = 0; i < m; i++) {
        groupGraph[i] = new ArrayList<>();
    }

    this.itemsIndegree = new int[n];
    this.groupsIndegree = new int[m];

    // builds items
    for (int i = 0; i < n; i++) {
        for (int item : beforeItems.get(i)) {
            itemGraph[item].add(i);
            itemsIndegrees[i]++;
        }
    }

    // builds group
    // multiple edges are possible
    for (int i = 0; i < group.length; i++) {
        int toGroup = group[i];
        for (int fromItem : beforeItems.get(i)) {
            int fromGroup = group[fromItem];
            if (fromGroup != toGroup) {
                groupGraph[fromGroup].add(toGroup);
                groupsIndegrees[toGroup]++;
            }
        }
    }

    // topological sort
    List<Integer> itemsList = sort(itemGraph, itemsIndegree, n);
    List<Integer> groupsList = sort(groupGraph, groupsIndegree, m);

    // detects if there are any cycles
    if (groupsList.isEmpty() || itemsList.isEmpty()) {
        return new int[0];
    }

    // maps items to their group in order
    List<Integer>[] membersInGroup = new ArrayList[m];
    for (int i = 0; i < m; i++) {
        membersInGroup[i] = new ArrayList<>();
    }
    for (int item : itemsList) {
        membersInGroup[group[item]].add(item);
    }

    int[] result = new int[n];
    int index = 0;
    for (int g : groupsList) {
        for (int item : membersInGroup[g]) {
            result[index++] = item;
        }
    }
    return result;
}

private List<Integer> sort(List<Integer>[] graph, int[] indegree, int count) {
    List <Integer> list = new ArrayList<>();
    Queue <Integer> q = new LinkedList<>();
    for (int i = 0; i < graph.length; i++) {
        if (indegrees[i] == 0) {
            q.offer(i);
        }
    }

    while (!q.isEmpty()) {
        int node = q.poll();
        count--;
        list.add(node);
        for (int neighbor : graph[node]) {
            if (--indegrees[neighbor] == 0) {
                q.offer(neighbor);
            }
        }
    }
    return count == 0 ? list : Collections.EMPTY_LIST;
}

Another solution is by DFS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
private List<Integer>[] graph;
private int[] indegree;
private int n;

public int[] sortItems(int n, int m, int[] group, List<List<Integer>> beforeItems) {
    this.n = n;

    // items that belong to the same group are virtually bundled together
    // n <= group node index <= n + m
    //
    // if k < n
    //   if k is in a group
    //     - graph[k] is inner-group after-items of item k
    //     - indegrees[k] is inner-group indegree of item k
    //   else if k is not in any group
    //     - graph[k] is after-items of item k
    //     - indegrees[k] is indegree of item k
    // otherwise
    //   - graph[k] is members of group (k - n)
    //   - indegrees[k] is indegree of group (k - n)
    this.graph = new ArrayList[n + m];
    this.indegree = new int[n + m];

    for (int i = 0; i < n; i++) {
        graph[i] = new ArrayList<>();
    }

    for (int i = 0; i < n; i++) {
        // bundles nodes that belong to the same group
        // the new index becomes: n + group[i]
        if (group[i] >= 0) {
            graph[n + group[i]].add(i);
            // marks indegree as 1
            // so when we scan to find the 0-indegree node
            // if index < n, it guarantees to be a node that doesn't belong to a group
            indegrees[i]++;
        }
    }

    for (int i = 0; i < n; i++) {
        int ig = map(group, i);
        for (int b : beforeItems.get(i)) {
            int bg = map(group, b);

            // current item and its before item are in the same group
            if (bg == ig) {
                graph[b].add(i);
                // remember, inner-group indegrees start from 1
                indegrees[i]++;
            } else {
                // either i is not in a group
                // or i and b are in different groups
                // this is for "external" links
                graph[bg].add(ig);
                indegrees[ig]++;
            }
        }
    }

    List<Integer> list = new ArrayList<>();
    for (int i = 0; i < indegree.length; i++) {
        // when indegrees[i] == 0
        // if i < n, the node i doesn't belong to any group (see comments above)
        // if i >= n, the dfs topologically sorts the members in the group
        if (indegrees[i] == 0) {
            dfs(i, list);
        }
    }

    return list.size() == n ? list.stream().mapToInt(i -> i).toArray() : new int[0];
}

// maps the item to its group node index if it belongs to a group
// otherwise returns its item index
private int map(int[] group, int item) {
    return group[item] < 0 ? item : group.length + group[item];
}

private void dfs(int curr, List<Integer> list) {
    if (curr < n) {
        list.add(curr);
    }

    // marks it as visited (-1)
    // so the start condition of the dfs indegrees[node] == 0 won't be met again
    indegrees[curr]--;

    // if curr < n, and
    // - curr doesn't belong to any group, then graph[curr] is its after-items
    // - curr belongs to a group, then group[curr] is the after-items of the same groups
    // otherwise, graph[curr] are the members of the group, and only members with degree 1 will be picked
    for (int child : graph[curr]) {
        // remember, inner-group indegrees are based on 1
        if (--indegrees[child] == 0) {
            dfs(child, list);
        }
    }
}

Centroids

Minimum Height Trees

Any connected graph without simple cycles is a tree. The number of centroids of a tree is no more than 2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
    if (n == 1) {
        return Collections.singletonList(0);
    }

    List<Set<Integer>> tree = new ArrayList<>();
    for (int i = 0; i < n; i++) {
        tree.add(new HashSet<>());
    }
    for (int[] e : edges) {
        tree.get(e[0]).add(e[1]);
        tree.get(e[1]).add(e[0]);
    }

    // finds the leaves
    Queue<Integer> q = new LinkedList<>();
    for (int i = 0; i < n; i++) {
        if (tree.get(i).size() == 1) {
            q.offer(i);
        }
    } 

    // trims the leaves until centroids
    while (n > 2) {
        n -= q.size();
        for (int i = q.size(); i > 0; i--) {
            int leaf = q.poll();
            int neighbor = tree.get(leaf).iterator().next();
            tree.get(neighbor).remove(leaf);

            // undirected graph leaf
            if (tree.get(neighbor).size() == 1) {
                q.offer(neighbor);
            }
        }
    }
    return new ArrayList<>(q);
}

Diameter

Tree Diameter

diameter = 2 * level + number of centroids - 1

Count Subtrees With Max Distance Between Cities

+ Other Algorithms

+ DP

dp[node] denotes a certain state of all paths to this node.

Shortest Path

Parallel Courses III

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public int minimumTime(int n, int[][] relations, int[] time) {
    List<Integer>[] graph = new List[n + 1];
    for (int i = 1; i <= n; i++) {
        graph[i] = new ArrayList<>();
    }

    int[] indegree = new int[n + 1];
    for (int[] r : relations) {
        graph[r[0]].add(r[1]);
        indegrees[r[1]]++;
    }

    // DP: minimum time to reach this node
    int[] cost = new int[n + 1];
    Queue<Integer> q = new LinkedList<>();
    for (int i = 1; i <= n; i++) {
        if (indegrees[i] == 0) {
            q.offer(i);
            cost[i] = time[i - 1];
        }
    }

    while (!q.isEmpty()) {
        int node = q.poll();
        for (int neighbor : graph[node]) {
            cost[neighbor] = Math.max(cost[neighbor], cost[node] + time[neighbor - 1]);
            if (--indegrees[neighbor] == 0) {
                q.offer(neighbor);
            }
        }
    }
    return Arrays.stream(cost).max().getAsInt();
}

Highest Frequency

Largest Color Value in a Directed Graph

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public int largestPathValue(String colors, int[][] edges) {
    int n = colors.length();
    List<Integer>[] graph = new List[n];
    for (int i = 0; i < n; i++) {
        graph[i] = new ArrayList<>();
    }

    int[] indegree = new int[n];
    for (int[] e : edges) {
        graph[e[0]].add(e[1]);
        indegrees[e[1]]++;
    }

    // dp[i][j]: max count of i-th node, j-th color
    int[][] dp = new int[n][26];

    // zero indegree
    Queue<Integer> q = new LinkedList<>();
    for (int i = 0; i < n; i++) {
        if (indegrees[i] == 0) {
            q.offer(i);
            dp[i][colors.charAt(i) - 'a'] = 1;
        }
    }

    int count = 0, max = 0;
    while (!q.isEmpty()) {
        int node = q.poll();
        count++;

        // if max is updated at this node
        // then the color of this node must be the most frequent
        max = Math.max(max, dp[node][colors.charAt(node) - 'a']);

        for (int child : graph[node]) {
            // updates dp of child node
            for (int i = 0; i < 26; i++) {
                dp[child][i] = Math.max(dp[child][i], dp[node][i] + (colors.charAt(child) - 'a' == i ? 1 : 0));
            }

            if (--indegrees[child] == 0) {
                q.offer(child);
            }
        }
    }
    return count == n ? max : -1;
}
This post is licensed under CC BY 4.0 by the author.