|
2 | 2 | from collections import defaultdict |
3 | 3 |
|
4 | 4 |
|
5 | | -class Solution: |
| 5 | +def min_reorder(n: int, connections: List[List[int]]) -> int: |
| 6 | + """Reorders the edges of the directed graph represented as an adjacency list in connections such that each vertex |
| 7 | + is connected and has a path to the first vertex marked with 0. n represents the number of vertices. |
| 8 | +
|
| 9 | + Complexity; |
| 10 | + n is the number of vertices/nodes |
| 11 | +
|
| 12 | + Time Complexity: O(n) |
| 13 | + O(n) to initialize the adjacency list |
| 14 | + The dfs function visits each node once, which takes O(n) time in total. Because we have undirected edges, |
| 15 | + each edge can only be iterated twice (by nodes at the end), resulting in O(e) operations total while |
| 16 | + visiting all nodes, where e is the number of edges. Because the given graph is a tree, there are n−1 |
| 17 | + undirected edges, so O(n+e)=O(n). |
| 18 | +
|
| 19 | + Space Complexity: O(n) |
| 20 | + Building the adjacency list takes O(n) space. |
| 21 | + The recursion call stack used by dfs can have no more than n elements in the worst-case scenario. |
| 22 | + It would take up O(n) space in that case. |
| 23 | +
|
| 24 | + Args: |
| 25 | + n(int): number of vertices or in this case, number of cities |
| 26 | + connections(list): adjacency matrix for a directed graph or in this case, representation of cities |
| 27 | + Returns: |
| 28 | + int: minimum number of edges to re-arrange to ensure that each vertex is directly or indirectly connected to |
| 29 | + the initial vertex |
| 30 | + """ |
| 31 | + |
| 32 | + # Adjacency list that contains list of pairs of nodes such that adj[node] contains all the neighbours of node in the |
| 33 | + # form of [neighbour, sign] where neighbour is the neighbouring node and sign is the direction of the edge. If the |
| 34 | + # sign is 0, it's an 'artificial' edge, meaning it was added by the algorithm in order to get to this vertex, and 1 |
| 35 | + # denotes that it's an 'original' edge, meaning that it's the original edge and no need to re-order that connection |
| 36 | + adj: Dict[int, List[List[int]]] = defaultdict(lambda: []) |
| 37 | + |
6 | 38 | # keep track of number of reorders made |
7 | 39 | reorder_count = 0 |
8 | 40 |
|
9 | | - def min_reorder(self, n: int, connections: List[List[int]]) -> int: |
10 | | - """Reorders the edges of the directed graph represented as an adjacency list in connections such that each vertex |
11 | | - is connected and has a path to the first vertex marked with 0. n represents the number of vertices. |
12 | | -
|
13 | | - Complexity; |
14 | | - n is the number of vertices/nodes |
15 | | -
|
16 | | - Time Complexity: O(n) |
17 | | - O(n) to initialize the adjacency list |
18 | | - The dfs function visits each node once, which takes O(n) time in total. Because we have undirected edges, |
19 | | - each edge can only be iterated twice (by nodes at the end), resulting in O(e) operations total while |
20 | | - visiting all nodes, where e is the number of edges. Because the given graph is a tree, there are n−1 |
21 | | - undirected edges, so O(n+e)=O(n). |
22 | | -
|
23 | | - Space Complexity: O(n) |
24 | | - Building the adjacency list takes O(n) space. |
25 | | - The recursion call stack used by dfs can have no more than n elements in the worst-case scenario. |
26 | | - It would take up O(n) space in that case. |
27 | | -
|
28 | | - Args: |
29 | | - n(int): number of vertices or in this case, number of cities |
30 | | - connections(list): adjacency matrix for a directed graph or in this case, representation of cities |
31 | | - Returns: |
32 | | - int: minimum number of edges to re-arrange to ensure that each vertex is directly or indirectly connected to |
33 | | - the initial vertex |
34 | | - """ |
35 | | - |
36 | | - # Adjacency list that contains list of pairs of nodes such that adj[node] contains all the neighbours of node in the |
37 | | - # form of [neighbour, sign] where neighbour is the neighbouring node and sign is the direction of the edge. If the |
38 | | - # sign is 0, it's an 'artificial' edge, meaning it was added by the algorithm in order to get to this vertex, and 1 |
39 | | - # denotes that it's an 'original' edge, meaning that it's the original edge and no need to re-order that connection |
40 | | - adj: Dict[int, List[List[int]]] = defaultdict(lambda: []) |
41 | | - |
42 | | - def dfs(node: int, parent: int, adjacency: Dict[int, List[List[int]]]): |
43 | | - if node not in adjacency: |
44 | | - return |
45 | | - |
46 | | - # iterate over all children of node(nodes that share an edge) |
47 | | - # for every child, sign, check if child is equal to parent. if child is equal to parent, we will not visit it |
48 | | - # again |
49 | | - # if child is not equal to parent, we perform count+=sign and recursively call the dfs with node = child and |
50 | | - # parent = node |
51 | | - for adjacent_node in adjacency.get(node): |
52 | | - child = adjacent_node[0] |
53 | | - sign = adjacent_node[1] |
54 | | - |
55 | | - if child != parent: |
56 | | - self.reorder_count += sign |
57 | | - dfs(child, node, adjacency) |
58 | | - |
59 | | - for connection in connections: |
60 | | - adj[connection[0]].append([connection[1], 1]) |
61 | | - adj[connection[1]].append([connection[0], 0]) |
62 | | - |
63 | | - # we start with node, parent and the adjacency list as 0, and -1 and adj |
64 | | - dfs(0, -1, adj) |
65 | | - |
66 | | - return self.reorder_count |
| 41 | + def dfs(node: int, parent: int, adjacency: Dict[int, List[List[int]]]): |
| 42 | + nonlocal reorder_count |
| 43 | + if node not in adjacency: |
| 44 | + return |
| 45 | + |
| 46 | + # iterate over all children of node(nodes that share an edge) |
| 47 | + # for every child, sign, check if child is equal to parent. if child is equal to parent, we will not visit it |
| 48 | + # again |
| 49 | + # if child is not equal to parent, we perform count+=sign and recursively call the dfs with node = child and |
| 50 | + # parent = node |
| 51 | + for adjacent_node in adjacency.get(node): |
| 52 | + child = adjacent_node[0] |
| 53 | + sign = adjacent_node[1] |
| 54 | + |
| 55 | + if child != parent: |
| 56 | + reorder_count += sign |
| 57 | + dfs(child, node, adjacency) |
| 58 | + |
| 59 | + for connection in connections: |
| 60 | + adj[connection[0]].append([connection[1], 1]) |
| 61 | + adj[connection[1]].append([connection[0], 0]) |
| 62 | + |
| 63 | + # we start with node, parent and the adjacency list as 0, and -1 and adj |
| 64 | + dfs(0, -1, adj) |
| 65 | + |
| 66 | + return reorder_count |
| 67 | + |
| 68 | + |
| 69 | +def min_reorder_2(n: int, connections: List[List[int]]) -> int: |
| 70 | + def dfs(city, graph, visited): |
| 71 | + visited.add(city) |
| 72 | + reversals = 0 |
| 73 | + for neighbor, need_reverse in graph[city]: |
| 74 | + if neighbor not in visited: |
| 75 | + reversals += need_reverse |
| 76 | + reversals += dfs(neighbor, graph, visited) |
| 77 | + return reversals |
| 78 | + |
| 79 | + graph = defaultdict(list) |
| 80 | + for ai, bi in connections: |
| 81 | + graph[ai].append((bi, 1)) |
| 82 | + graph[bi].append((ai, 0)) |
| 83 | + |
| 84 | + visited = set() |
| 85 | + result = dfs(0, graph, visited) |
| 86 | + return result |
0 commit comments