Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 6b0e671

Browse files
Merge pull request #76 from amejiarosario/feat/graph-questions
Feat/graph questions
2 parents 8a92ba0 + 6a83cf8 commit 6b0e671

11 files changed

+587
-8
lines changed

‎book/D-interview-questions-solutions.asc

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,3 +655,194 @@ We could also have used a Map and keep track of the indexes, but that's not nece
655655

656656
- Time: `O(n)`. We visit each letter once.
657657
- 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`.
658+
659+
660+
661+
662+
663+
664+
665+
:leveloffset: +1
666+
667+
=== Solutions for Graph Questions
668+
(((Interview Questions Solutions, Graph)))
669+
670+
:leveloffset: -1
671+
672+
673+
[#graph-q-course-schedule]
674+
include::content/part03/graph-search.asc[tag=graph-q-course-schedule]
675+
676+
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!
703+
704+
[source, javascript]
705+
----
706+
include::interview-questions/course-schedule.js[tags=brute1]
707+
----
708+
709+
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!
731+
** If we see a node in a visited state, skip it.
732+
733+
*Implementation*:
734+
735+
[source, javascript]
736+
----
737+
include::interview-questions/course-schedule.js[tags=description;solution]
738+
----
739+
740+
In the first line, we initialize the map with the course index and an empty array.
741+
This time the `seen` array is outside the recursion.
742+
743+
*Complexity Analysis*:
744+
745+
- Time: `O(|V| + |E|)`. We go through each node and edge only once.
746+
- Space: `O(|V| + |E|)`. The size of the adjacency list.
747+
748+
749+
750+
751+
//
752+
[#graph-q-critical-connections-in-a-network]
753+
include::content/part03/graph-search.asc[tag=graph-q-critical-connections-in-a-network]
754+
755+
On idea to find if a path is critical is to remove it. If we visit the graph and see that some nodes are not reachable, then, oops, It was critical!
756+
757+
We can code precisely that. We can remove one link at a time and check if all other nodes are reachable. It's not very efficient, but it's a start.
758+
759+
[source, javascript]
760+
----
761+
include::interview-questions/critical-connections-in-a-network.js[tags=criticalConnectionsBrute1]
762+
----
763+
764+
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!
767+
768+
*Tarjan's Strongly Connected Components Algorithms*
769+
770+
A connection is critical only if it's not part of the cycle.
771+
772+
In other words, a critical path is like a bridge that connects islands; if you remove it you won't cross from one island to the other.
773+
774+
Connections that are part of the cycle (blue) have redundancy. If you eliminate one, you can still reach other nodes. Check out the examples below.
775+
776+
[graphviz, critical-connections-sol-examples, png]
777+
....
778+
graph G {
779+
subgraph cluster_0 {
780+
a0 -- a1 [color=blue]
781+
a1 -- a2 [color=blue]
782+
a2 -- a0 [color=blue]
783+
a1 -- a3 [color=blue]
784+
a3 -- a2 [color=blue]
785+
label = "Example A";
786+
}
787+
788+
subgraph cluster_3 {
789+
b0 -- b1 [color=blue]
790+
b1 -- b2 [color=blue]
791+
b2 -- b0 [color=blue]
792+
b1 -- b3 [color=red]
793+
b3 -- b2 [color=transparent] // removed
794+
label = "Example B";
795+
}
796+
797+
subgraph cluster_1 {
798+
c0 -- c1 -- c2 -- c3 [color=red]
799+
label = "Example C";
800+
}
801+
}
802+
....
803+
804+
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.
823+
824+
*Implementation*:
825+
826+
[source, javascript]
827+
----
828+
include::interview-questions/critical-connections-in-a-network.js[tags=description;solution]
829+
----
830+
831+
This algorithm only works with DFS.
832+
833+
*Complexity Analysis*:
834+
835+
- Time: `O(|E| + |V|)`. We visit each node and edge only once.
836+
- Space: `O(|E| + |V|)`. The graph has all the edges and nodes. Additionally, we use the `group` variable with a size of `|V|`.
837+
838+
839+
840+
841+
842+
843+
844+
//
845+
846+
847+
848+

‎book/content/part03/graph-search.asc

Lines changed: 135 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ endif::[]
77

88
Graph search allows you to visit search elements.
99

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.
1111

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.
1313

1414
image::directed-graph.png[directed graph]
1515

@@ -44,10 +44,10 @@ image::directed-graph.png[directed graph]
4444

4545
==== Depth-First Search for Graphs
4646

47-
With Depth-First Search (DFS) we go deep before going wide.
47+
With Depth-First Search (DFS), we go deep before going wide.
4848

4949
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`.
5151
Finally, we back up to the remaining node `0` children: node `4`.
5252

5353
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]`.
5656

5757
==== Breadth-First Search for Graphs
5858

59-
With Breadth-First Search (BFS) we go wide before going deep.
59+
With Breadth-First Search (BFS), we go wide before going deep.
6060

6161
// TODO: BFS traversal
6262
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.
6464
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`.
6666

6767
In summary, BFS would visit the graph: `[0, 5, 1, 4, 3, 2]`
6868

@@ -86,4 +86,131 @@ You might wonder what the difference between search algorithms in a tree and a g
8686

8787
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.
8888

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)._
106+
107+
108+
*Starter code*:
109+
110+
[source, javascript]
111+
----
112+
include::../../interview-questions/course-schedule.js[tags=description;placeholder]
113+
----
114+
115+
116+
*Examples*:
117+
118+
[source, javascript]
119+
----
120+
canFinish(2, [[1, 0]]); // true
121+
// 2 courses: 0 and 1. One prerequisite: 0 -> 1
122+
// To take course 1 you need to take course 0.
123+
// Course 0 has no prerequisite, so you can take 0 and then 1.
124+
125+
canFinish(2, [[1, 0], [0, 1]]); // false
126+
// 2 courses: 0 and 1. Two prerequisites: 0 -> 1 and 1 -> 0.
127+
// To take course 1, you need to take course 0.
128+
// To Course 0, you need course 1, so you can't any take them!
129+
130+
canFinish(3, [[2, 0], [1, 0], [2, 1]]); // true
131+
// 3 courses: 0, 1, 2. Three prerequisites: 0 -> 2 and 0 -> 1 -> 2
132+
// To take course 2 you need course 0, course 0 has no prerequisite.
133+
// So you can take course 0 first, then course 1, and finally course 2.
134+
135+
canFinish(4, [[1, 0], [2, 1], [3, 2], [1, 3]]); // false
136+
// 4 courses: 0, 1, 2, 3. Prerequisites: 0 -> 1 -> 2 -> 3 and 3 -> 1.
137+
// You can take course 0 first since it has no prerequisite.
138+
// For taking course 1, you need course 3. However, for taking course 3
139+
// you need 2 and 1. You can't finish then!
140+
----
141+
142+
143+
_Solution: <<graph-q-course-schedule>>_
144+
145+
146+
147+
148+
149+
150+
151+
// tag::graph-q-critical-connections-in-a-network[]
152+
===== Critical Network Paths
153+
154+
*gr-2*) _Given `n` servers and the connections between them, return the critical paths._
155+
156+
// end::graph-q-critical-connections-in-a-network[]
157+
158+
// _Seen in interviews at: Amazon, Google._
159+
160+
Examples:
161+
162+
[graphviz, critical-path-examples, png]
163+
....
164+
graph G {
165+
subgraph cluster_1 {
166+
a0 -- a1 -- a2 [color=firebrick1]
167+
label = "Example A";
168+
}
169+
170+
subgraph cluster_0 {
171+
b0 -- b1 [color=blue]
172+
b1 -- b2 [color=blue]
173+
b2 -- b0 [color=blue]
174+
b1 -- b3 [color=blue]
175+
b3 -- b2 [color=blue]
176+
label = "Example B";
177+
b0, b1, b2, b3 [color=midnightblue]
178+
}
179+
180+
subgraph cluster_3 {
181+
c0 -- c1 [color=blue]
182+
c1 -- c2 [color=blue]
183+
c2 -- c0 [color=blue]
184+
c1 -- c3 [color=firebrick1]
185+
c3 -- c2 [color=transparent] // removed
186+
label = "Example C";
187+
c0, c1, c2 [color=midnightblue]
188+
// c3 [color=red]
189+
}
190+
}
191+
....
192+
193+
[source, javascript]
194+
----
195+
// Example A
196+
criticalConnections(3, [[0, 1], [1, 2]]);// [[0, 1], [1, 2]]
197+
// if you remove any link, there will be stranded servers.
198+
199+
// Example B
200+
criticalConnections(4, [[0, 1], [1, 2], [2, 0], [1, 3], [3, 2]]);// []
201+
// you can remove any connection and all servers will be reachable.
202+
203+
// Example C
204+
criticalConnections(4, [[0, 1], [1, 2], [2, 0], [1, 3]]); // [[1, 3]]
205+
// if you remove [1, 3], then server 3 won't be reachable.
206+
// If you remove any other link. It will be fine.
207+
----
208+
209+
Starter code:
210+
211+
[source, javascript]
212+
----
213+
include::../../interview-questions/critical-connections-in-a-network.js[tags=description;placeholder]
214+
----
215+
216+
_Solution: <<graph-q-critical-connections-in-a-network>>_
22 KB
Loading[フレーム]
34.8 KB
Loading[フレーム]

‎book/images/critical-path-examples.png

39.9 KB
Loading[フレーム]

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /