You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: book/D-interview-questions-solutions.asc
+191
Original file line number
Diff line number
Diff line change
@@ -655,3 +655,194 @@ We could also have used a Map and keep track of the indexes, but that's not nece
655
655
656
656
- Time: `O(n)`. We visit each letter once.
657
657
- Space: `O(W)`, where `W` is the max length of non-repeating characters. The maximum size of the Set gives the space complexity. In the worst-case scenario, all letters are unique (`W = n`), so our space complexity would be `O(n)`. In the avg. case where there are one or more duplicates, it uses less space than `n`, because `W < n`.
Basically, we have to detect if the graph has a cycle or not.
677
+
There are multiple ways to detect cycles on a graph using BFS and DFS.
678
+
679
+
One of the most straightforward ways to do it is using DFS one each course (node) and traverse their prerequisites (neighbors). If we start in a node, and then we see that node again, we found a cycle! (maybe)
680
+
681
+
A critical part of solving this exercise is coming up with good test cases. Let's examine these two:
682
+
683
+
[graphviz, course-schedule-examples, png]
684
+
....
685
+
digraph G {
686
+
subgraph cluster_1 {
687
+
a0 -> a1 -> a2
688
+
a0 -> a2 [color=gray]
689
+
label = "Example A"
690
+
}
691
+
692
+
subgraph cluster_2 {
693
+
b0 -> b1 -> b2 -> b3
694
+
b3 -> b1 [color=red]
695
+
label = "Example B";
696
+
}
697
+
}
698
+
....
699
+
700
+
Let's say we are using a regular DFS, where we visit the nodes and keep track of visited nodes. If we test the example A, we can get to the course 2 (a2) in two ways. So, we can't blindly assume that "seen" nodes are because of a cycle. To solve this issue, we can keep track of the parent.
701
+
702
+
For example B, if we start in course 0 (b0), we can find a cycle. However, the cycle does not involve course 0 (parent). When we visit course 1 (b1) and mark it as the parent, we will see that reach to course 1 (b1) again. Then, we found a cycle!
We built the graph on the fly as an adjacency list (Map + Arrays).
710
+
Then we visited each node, checking if there it has cycles. If none has cyles, then we return true.
711
+
712
+
The cycle check uses DFS. We keep track of seen nodes and also who the parent is. If we get to the parent more than once, we have a cycle like examples A and B.
713
+
714
+
What's the time complexity?
715
+
716
+
We visite every node/vertex: `O(|V|)` and then for every node, we visite all it's edges, so we have `O(|V|*|E|)`.
717
+
718
+
Can we do better?
719
+
720
+
There's no need to visit nodes more than once. Instead of having a local `seen` variable for each node, we can move it outside the loop. However, it won't be a boolean anymore (seen or not seen). We could see nodes more than once, without being in a cycle (example A). One idea is to have 3 states: `unvisited` (0), `visiting` (1) and `visited` (2). Let's devise the algorithm:
721
+
722
+
*Algorithm*:
723
+
724
+
* Build a graph as an adjacency list (map + arrays).
725
+
* Fill in every prerequisite as an edge on the graph.
726
+
* Visit every node and if there's a cycle, return false.
727
+
** When we start visiting a node, we mark it as 1 (visiting)
728
+
** Visit all its adjacent nodes
729
+
** Mark current node as 2 (visited) when we finish visiting neighbors.
730
+
** If we see a node in visiting state more than once, it's a cycle!
We are using a function `areAllNodesReachable`, which implements a BFS for visiting the graph, but DFS would have worked too. The runtime is `O(|E| + |V|)`, where `E` is the number of edges and `V` the number of nodes/servers. In `criticalConnectionsBrute1`, We are looping through all `connections` (`E`) to remove one connection at a time and then checking if all servers are still reachable with `areAllNodesReachable`.
765
+
766
+
The time complexity is `O(|E|^2 * |V|)`. Can we do it on one pass? Sure we can!
The red connections are critical; if we remove any, some servers won't be reachable.
805
+
806
+
We can solve this problem in one pass using DFS. But for that, we keep track of the nodes that are part of a loop (strongly connected components). To do that, we use the time of visit (or depth in the recursion) each node.
807
+
808
+
For example C, if we start on `c0`, it belongs to group 0, then we move c1, c2, and c3, increasing the depth counter. Each one will be on its own group since there's no loop.
809
+
810
+
For example B, we can start at `b0`, and then we move to `b1` and `b2`. However, `b2` circles back to `b0`, which is on group 0. We can update the group of `b1` and `b2` to be 0 since they are all connected in a loop.
811
+
812
+
For an *undirected graph*, If we found a node on our dfs, that we have previously visited, we found a loop! We can mark all of them with the lowest group number. We know we have a critical path when it's a connection that links two different groups. For example A, they all will belong to group 0, since they are all in a loop. For Example B, we will have `b0`, `b1`, and `b2` on the same group while `b3` will be on a different group.
813
+
814
+
*Algorithm*:
815
+
816
+
* Build the graph as an adjacency list (map + array)
817
+
* Run dfs on any node. E.g. `0`.
818
+
** Keep track of the nodes that you have seen using `group` array. But instead of marking them as seen or not. Let's mark it with the `depth`.
819
+
** Visit all the adjacent nodes that are NOT the parent.
820
+
** If we see a node that we have visited yet, do a dfs on it and increase the depth.
821
+
** If the adjacent node has a lower grouping number, update the current node with it.
822
+
** If the adjacent node has a higher grouping number, then we found a critical path.
Copy file name to clipboardExpand all lines: book/content/part03/graph-search.asc
+135-8
Original file line number
Diff line number
Diff line change
@@ -7,9 +7,9 @@ endif::[]
7
7
8
8
Graph search allows you to visit search elements.
9
9
10
-
WARNING: Graph search is very similar to <<Tree Search & Traversal>>. So, if you read that sections some of the concepts here will be familiar to you.
10
+
WARNING: Graph search is very similar to <<Tree Search & Traversal>>. So, if you read that section, some of the concepts here will be familiar to you.
11
11
12
-
There are two ways to navigate the graph, one is using Depth-First Search (DFS) and the other one is Breadth-First Search (BFS). Let's see the difference using the following graph.
12
+
There are two ways to navigate the graph, one is using Depth-First Search (DFS), and the other one is Breadth-First Search (BFS). Let's see the difference using the following graph.
With Depth-First Search (DFS) we go deep before going wide.
47
+
With Depth-First Search (DFS), we go deep before going wide.
48
48
49
49
Let's say that we use DFS on the graph shown above, starting with node `0`.
50
-
A DFS, will probably visit 5, then visit `1` and continue going down `3` and `2`. As you can see, we need to keep track of visited nodes, since in graphs we can have cycles like `1-3-2`.
50
+
A DFS will probably visit 5, then visit `1` and continue going down `3` and `2`. As you can see, we need to keep track of visited nodes, since in graphs, we can have cycles like `1-3-2`.
51
51
Finally, we back up to the remaining node `0` children: node `4`.
52
52
53
53
So, DFS would visit the graph: `[0, 5, 1, 3, 2, 4]`.
@@ -56,13 +56,13 @@ So, DFS would visit the graph: `[0, 5, 1, 3, 2, 4]`.
56
56
57
57
==== Breadth-First Search for Graphs
58
58
59
-
With Breadth-First Search (BFS) we go wide before going deep.
59
+
With Breadth-First Search (BFS), we go wide before going deep.
60
60
61
61
// TODO: BFS traversal
62
62
Let's say that we use BFS on the graph shown above, starting with the same node `0`.
63
-
A BFS, will visit 5 as well, then visit `1` and will not go down to it's children.
63
+
A BFS will visit 5 as well, then visit `1` and not go down to its children.
64
64
It will first finish all the children of node `0`, so it will visit node `4`.
65
-
After all the children of node `0` are visited it continue with all the children of node `5`, `1` and `4`.
65
+
After all the children of node `0` are visited, it will continue with all the children of node `5`, `1`, and `4`.
66
66
67
67
In summary, BFS would visit the graph: `[0, 5, 1, 4, 3, 2]`
68
68
@@ -86,4 +86,131 @@ You might wonder what the difference between search algorithms in a tree and a g
86
86
87
87
The difference between searching a tree and a graph is that the tree always has a starting point (root node). However, in a graph, you can start searching anywhere. There's no root.
88
88
89
-
NOTE: Every tree is a graph, but not every graph is a tree.
89
+
NOTE: Every tree is a graph, but not every graph is a tree. Only acyclic directed graphs (DAG) are trees.
90
+
91
+
92
+
==== Practice Questions
93
+
(((Interview Questions, graph)))
94
+
95
+
96
+
97
+
98
+
// tag::graph-q-course-schedule[]
99
+
===== Course Schedule
100
+
101
+
*gr-1*) _Check if it's possible to take a number of courses while satisfying their prerequisites._
102
+
103
+
// end::graph-q-course-schedule[]
104
+
105
+
// _Seen in interviews at: Amazon, Facebook, Bytedance (TikTok)._
0 commit comments