diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4bd0eb14..e48741f9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+# [1.18.0](https://github.com/amejiarosario/dsa.js/compare/1.17.0...1.18.0) (2020年09月07日)
+
+
+### Features
+
+* **book/graph:** add schedule exercise and solution ([6a83cf8](https://github.com/amejiarosario/dsa.js/commit/6a83cf8a5d454b26e3048aa4ca73e44eafca0ed3))
+
# [1.17.0](https://github.com/amejiarosario/dsa.js/compare/1.16.0...1.17.0) (2020年09月04日)
diff --git a/book/D-interview-questions-solutions.asc b/book/D-interview-questions-solutions.asc
index 180a6222..d7228268 100644
--- a/book/D-interview-questions-solutions.asc
+++ b/book/D-interview-questions-solutions.asc
@@ -655,3 +655,194 @@ We could also have used a Map and keep track of the indexes, but that's not nece
- Time: `O(n)`. We visit each letter once.
- 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`. + + + + + + + +:leveloffset: +1 + +=== Solutions for Graph Questions +(((Interview Questions Solutions, Graph))) + +:leveloffset: -1 + + +[#graph-q-course-schedule] +include::content/part03/graph-search.asc[tag=graph-q-course-schedule] + +Basically, we have to detect if the graph has a cycle or not. +There are multiple ways to detect cycles on a graph using BFS and DFS. + +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) + +A critical part of solving this exercise is coming up with good test cases. Let's examine these two: + +[graphviz, course-schedule-examples, png] +.... +digraph G { + subgraph cluster_1 { + a0 -> a1 -> a2
+ a0 -> a2 [color=gray]
+ label = "Example A"
+ }
+
+ subgraph cluster_2 {
+ b0 -> b1 -> b2 -> b3
+ b3 -> b1 [color=red]
+ label = "Example B";
+ }
+}
+....
+
+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.
+
+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!
+
+[source, javascript]
+----
+include::interview-questions/course-schedule.js[tags=brute1]
+----
+
+We built the graph on the fly as an adjacency list (Map + Arrays).
+Then we visited each node, checking if there it has cycles. If none has cyles, then we return true.
+
+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.
+
+What's the time complexity?
+
+We visite every node/vertex: `O(|V|)` and then for every node, we visite all it's edges, so we have `O(|V|*|E|)`.
+
+Can we do better?
+
+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:
+
+*Algorithm*:
+
+* Build a graph as an adjacency list (map + arrays).
+* Fill in every prerequisite as an edge on the graph.
+* Visit every node and if there's a cycle, return false.
+** When we start visiting a node, we mark it as 1 (visiting)
+** Visit all its adjacent nodes
+** Mark current node as 2 (visited) when we finish visiting neighbors.
+** If we see a node in visiting state more than once, it's a cycle!
+** If we see a node in a visited state, skip it.
+
+*Implementation*:
+
+[source, javascript]
+----
+include::interview-questions/course-schedule.js[tags=description;solution]
+----
+
+In the first line, we initialize the map with the course index and an empty array.
+This time the `seen` array is outside the recursion.
+
+*Complexity Analysis*:
+
+- Time: `O(|V| + |E|)`. We go through each node and edge only once.
+- Space: `O(|V| + |E|)`. The size of the adjacency list.
+
+
+
+
+//
+[#graph-q-critical-connections-in-a-network]
+include::content/part03/graph-search.asc[tag=graph-q-critical-connections-in-a-network]
+
+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!
+
+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.
+
+[source, javascript]
+----
+include::interview-questions/critical-connections-in-a-network.js[tags=criticalConnectionsBrute1]
+----
+
+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`.
+
+The time complexity is `O(|E|^2 * |V|)`. Can we do it on one pass? Sure we can!
+
+*Tarjan's Strongly Connected Components Algorithms*
+
+A connection is critical only if it's not part of the cycle.
+
+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.
+
+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.
+
+[graphviz, critical-connections-sol-examples, png]
+....
+graph G {
+ subgraph cluster_0 {
+ a0 -- a1 [color=blue]
+ a1 -- a2 [color=blue]
+ a2 -- a0 [color=blue]
+ a1 -- a3 [color=blue]
+ a3 -- a2 [color=blue]
+ label = "Example A";
+ }
+
+ subgraph cluster_3 {
+ b0 -- b1 [color=blue]
+ b1 -- b2 [color=blue]
+ b2 -- b0 [color=blue]
+ b1 -- b3 [color=red]
+ b3 -- b2 [color=transparent] // removed
+ label = "Example B";
+ }
+
+ subgraph cluster_1 {
+ c0 -- c1 -- c2 -- c3 [color=red]
+ label = "Example C";
+ }
+}
+....
+
+The red connections are critical; if we remove any, some servers won't be reachable.
+
+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.
+
+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.
+
+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.
+
+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.
+
+*Algorithm*:
+
+* Build the graph as an adjacency list (map + array)
+* Run dfs on any node. E.g. `0`.
+** 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`.
+** Visit all the adjacent nodes that are NOT the parent.
+** If we see a node that we have visited yet, do a dfs on it and increase the depth.
+** If the adjacent node has a lower grouping number, update the current node with it.
+** If the adjacent node has a higher grouping number, then we found a critical path.
+
+*Implementation*:
+
+[source, javascript]
+----
+include::interview-questions/critical-connections-in-a-network.js[tags=description;solution]
+----
+
+This algorithm only works with DFS.
+
+*Complexity Analysis*:
+
+- Time: `O(|E| + |V|)`. We visit each node and edge only once.
+- Space: `O(|E| + |V|)`. The graph has all the edges and nodes. Additionally, we use the `group` variable with a size of `|V|`.
+
+
+
+
+
+
+
+//
+
+
+
+
diff --git a/book/content/part03/graph-search.asc b/book/content/part03/graph-search.asc
index 48bf5308..08356768 100644
--- a/book/content/part03/graph-search.asc
+++ b/book/content/part03/graph-search.asc
@@ -7,9 +7,9 @@ endif::[]
Graph search allows you to visit search elements.
-WARNING: Graph search is very similar to <
>. So, if you read that sections some of the concepts here will be familiar to you.
+WARNING: Graph search is very similar to <
>. So, if you read that section, some of the concepts here will be familiar to you.
-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.
+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.
image::directed-graph.png[directed graph]
@@ -44,10 +44,10 @@ image::directed-graph.png[directed graph]
==== Depth-First Search for Graphs
-With Depth-First Search (DFS) we go deep before going wide.
+With Depth-First Search (DFS), we go deep before going wide.
Let's say that we use DFS on the graph shown above, starting with node `0`.
-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`.
+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`.
Finally, we back up to the remaining node `0` children: node `4`.
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]`.
==== Breadth-First Search for Graphs
-With Breadth-First Search (BFS) we go wide before going deep.
+With Breadth-First Search (BFS), we go wide before going deep.
// TODO: BFS traversal
Let's say that we use BFS on the graph shown above, starting with the same node `0`.
-A BFS, will visit 5 as well, then visit `1` and will not go down to it's children.
+A BFS will visit 5 as well, then visit `1` and not go down to its children.
It will first finish all the children of node `0`, so it will visit node `4`.
-After all the children of node `0` are visited it continue with all the children of node `5`, `1` and `4`.
+After all the children of node `0` are visited, it will continue with all the children of node `5`, `1`, and `4`.
In summary, BFS would visit the graph: `[0, 5, 1, 4, 3, 2]`
@@ -86,4 +86,131 @@ You might wonder what the difference between search algorithms in a tree and a g
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.
-NOTE: Every tree is a graph, but not every graph is a tree.
+NOTE: Every tree is a graph, but not every graph is a tree. Only acyclic directed graphs (DAG) are trees.
+
+
+==== Practice Questions
+(((Interview Questions, graph)))
+
+
+
+
+// tag::graph-q-course-schedule[]
+===== Course Schedule
+
+*gr-1*) _Check if it's possible to take a number of courses while satisfying their prerequisites._
+
+// end::graph-q-course-schedule[]
+
+// _Seen in interviews at: Amazon, Facebook, Bytedance (TikTok)._
+
+
+*Starter code*:
+
+[source, javascript]
+----
+include::../../interview-questions/course-schedule.js[tags=description;placeholder]
+----
+
+
+*Examples*:
+
+[source, javascript]
+----
+canFinish(2, [[1, 0]]); // true
+// 2 courses: 0 and 1. One prerequisite: 0 -> 1
+// To take course 1 you need to take course 0.
+// Course 0 has no prerequisite, so you can take 0 and then 1.
+
+canFinish(2, [[1, 0], [0, 1]]); // false
+// 2 courses: 0 and 1. Two prerequisites: 0 -> 1 and 1 -> 0.
+// To take course 1, you need to take course 0.
+// To Course 0, you need course 1, so you can't any take them!
+
+canFinish(3, [[2, 0], [1, 0], [2, 1]]); // true
+// 3 courses: 0, 1, 2. Three prerequisites: 0 -> 2 and 0 -> 1 -> 2
+// To take course 2 you need course 0, course 0 has no prerequisite.
+// So you can take course 0 first, then course 1, and finally course 2.
+
+canFinish(4, [[1, 0], [2, 1], [3, 2], [1, 3]]); // false
+// 4 courses: 0, 1, 2, 3. Prerequisites: 0 -> 1 -> 2 -> 3 and 3 -> 1.
+// You can take course 0 first since it has no prerequisite.
+// For taking course 1, you need course 3. However, for taking course 3
+// you need 2 and 1. You can't finish then!
+----
+
+
+_Solution: <>_
+
+
+
+
+
+
+
+// tag::graph-q-critical-connections-in-a-network[]
+===== Critical Network Paths
+
+*gr-2*) _Given `n` servers and the connections between them, return the critical paths._
+
+// end::graph-q-critical-connections-in-a-network[]
+
+// _Seen in interviews at: Amazon, Google._
+
+Examples:
+
+[graphviz, critical-path-examples, png]
+....
+graph G {
+ subgraph cluster_1 {
+ a0 -- a1 -- a2 [color=firebrick1]
+ label = "Example A";
+ }
+
+ subgraph cluster_0 {
+ b0 -- b1 [color=blue]
+ b1 -- b2 [color=blue]
+ b2 -- b0 [color=blue]
+ b1 -- b3 [color=blue]
+ b3 -- b2 [color=blue]
+ label = "Example B";
+ b0, b1, b2, b3 [color=midnightblue]
+ }
+
+ subgraph cluster_3 {
+ c0 -- c1 [color=blue]
+ c1 -- c2 [color=blue]
+ c2 -- c0 [color=blue]
+ c1 -- c3 [color=firebrick1]
+ c3 -- c2 [color=transparent] // removed
+ label = "Example C";
+ c0, c1, c2 [color=midnightblue]
+ // c3 [color=red]
+ }
+}
+....
+
+[source, javascript]
+----
+// Example A
+criticalConnections(3, [[0, 1], [1, 2]]);// [[0, 1], [1, 2]]
+// if you remove any link, there will be stranded servers.
+
+// Example B
+criticalConnections(4, [[0, 1], [1, 2], [2, 0], [1, 3], [3, 2]]);// []
+// you can remove any connection and all servers will be reachable.
+
+// Example C
+criticalConnections(4, [[0, 1], [1, 2], [2, 0], [1, 3]]); // [[1, 3]]
+// if you remove [1, 3], then server 3 won't be reachable.
+// If you remove any other link. It will be fine.
+----
+
+Starter code:
+
+[source, javascript]
+----
+include::../../interview-questions/critical-connections-in-a-network.js[tags=description;placeholder]
+----
+
+_Solution: <>_
diff --git a/book/images/course-schedule-examples.png b/book/images/course-schedule-examples.png
new file mode 100644
index 00000000..348fe3de
Binary files /dev/null and b/book/images/course-schedule-examples.png differ
diff --git a/book/images/critical-connections-sol-examples.png b/book/images/critical-connections-sol-examples.png
new file mode 100644
index 00000000..eb3e568f
Binary files /dev/null and b/book/images/critical-connections-sol-examples.png differ
diff --git a/book/images/critical-path-examples.png b/book/images/critical-path-examples.png
new file mode 100644
index 00000000..63b0e330
Binary files /dev/null and b/book/images/critical-path-examples.png differ
diff --git a/book/interview-questions/course-schedule.js b/book/interview-questions/course-schedule.js
new file mode 100644
index 00000000..c3e8b354
--- /dev/null
+++ b/book/interview-questions/course-schedule.js
@@ -0,0 +1,61 @@
+// tag::description[]
+/**
+ * Check if you can finish all courses with their prerequisites.
+ * @param {number} n - The number of courses
+ * @param {[number, number][]} prerequisites - Array of courses pairs.
+ * E.g. [[200, 101]], to take course 202 you need course 101 first.
+ * @returns {boolean} - True = can finish all courses, False otherwise
+ */
+function canFinish(n, prerequisites) {
+ // end::description[]
+ // tag::placeholder[]
+ // write your code here...
+ // end::placeholder[]
+ // tag::solution[]
+ const graph = new Map(Array(n).fill().map((_, i) => ([i, []])));
+ prerequisites.forEach(([u, v]) => graph.get(v).push(u));
+
+ const seen = [];
+ const hasCycle = (node) => {
+ if (seen[node] === 1) return true; // if visiting, it's a cycle!
+ if (seen[node] === 2) return false; // if visited, skip it.
+
+ seen[node] = 1; // mark as visiting.
+ for (const adj of graph.get(node)) if (hasCycle(adj)) return true;
+ seen[node] = 2; // mark as visited.
+ return false;
+ };
+
+ for (let i = 0; i < n; i++) if (hasCycle(i)) return false; + return true; + // end::solution[] + // tag::description[] +} +// end::description[] + + +// tag::brute1[] +function canFinishBrute1(n, prerequisites) { + const graph = new Map(); // inialize adjacency list as map of arrays + for (let i = 0; i < n; i++) graph.set(i, []); // build nodes + prerequisites.forEach(([u, v]) => graph.get(v).push(u)); // edges
+
+ const hasCycles = (node, parent = node, seen = []) => {
+ for (const next of graph.get(node)) {
+ if (next === parent) return true;
+ if (seen[next]) continue;
+ seen[next] = true;
+ if (hasCycles(next, parent, seen)) return true;
+ }
+ return false;
+ };
+
+ for (let i = 0; i < n; i++) { + if (hasCycles(i)) return false; + } + + return true; +} +// end::brute1[] + +module.exports = { canFinish, canFinishBrute1 }; diff --git a/book/interview-questions/course-schedule.spec.js b/book/interview-questions/course-schedule.spec.js new file mode 100644 index 00000000..61bb9a5f --- /dev/null +++ b/book/interview-questions/course-schedule.spec.js @@ -0,0 +1,54 @@ +const { canFinish, canFinishBrute1 } = require('./course-schedule'); +// const { } = require('../../src/index'); + +[canFinish, canFinishBrute1].forEach((fn) => {
+ describe(`TOPIC: ${fn.name}`, () => {
+ it('should work with null/empty', () => {
+ const actual = [];
+ const expected = true;
+ expect(fn(0, actual)).toEqual(expected);
+ });
+
+ it('should work basic case', () => {
+ const actual = [[1, 0]];
+ const courses = 2;
+ const expected = true;
+ expect(fn(courses, actual)).toEqual(expected);
+ });
+
+ it('should detect cycle', () => {
+ const actual = [[0, 1], [1, 0]];
+ const courses = 2;
+ const expected = false;
+ expect(fn(courses, actual)).toEqual(expected);
+ });
+
+ it('multiple links to a node without cycle', () => {
+ const actual = [[2, 1], [1, 0], [2, 0]];
+ const courses = 3;
+ const expected = true;
+ expect(fn(courses, actual)).toEqual(expected);
+ });
+
+ it('multiple links to a node without cycle (different order)', () => {
+ const actual = [[2, 0], [1, 0], [2, 1]];
+ const courses = 3;
+ const expected = true;
+ expect(fn(courses, actual)).toEqual(expected);
+ });
+
+ it('indirect cycle', () => {
+ const actual = [[1, 0], [2, 1], [0, 2]];
+ const courses = 3;
+ const expected = false;
+ expect(fn(courses, actual)).toEqual(expected);
+ });
+
+ it('indirect cycle with nodes without indegrees', () => {
+ const actual = [[1, 0], [2, 1], [3, 2], [1, 3]];
+ const courses = 4;
+ const expected = false;
+ expect(fn(courses, actual)).toEqual(expected);
+ });
+ });
+});
diff --git a/book/interview-questions/critical-connections-in-a-network.js b/book/interview-questions/critical-connections-in-a-network.js
new file mode 100644
index 00000000..bd7bfe37
--- /dev/null
+++ b/book/interview-questions/critical-connections-in-a-network.js
@@ -0,0 +1,76 @@
+const { Queue } = require('../../src/index');
+
+// tag::description[]
+function criticalConnections(n, connections) {
+ // end::description[]
+ // tag::placeholder[]
+ // write your code here...
+ // end::placeholder[]
+ // tag::solution[]
+ const critical = [];
+ const graph = new Map(Array(n).fill(0).map((_, i) => [i, []]));
+ connections.forEach(([u, v]) => {
+ graph.get(u).push(v);
+ graph.get(v).push(u);
+ });
+
+ const dfs = (node, parent = null, depth = 0, group = []) => {
+ group[node] = depth;
+ for (const adj of (graph.get(node) || [])) {
+ if (adj === parent) continue; // skip parent node
+ if (group[adj] === undefined) dfs(adj, node, depth + 1, group);
+ group[node] = Math.min(group[node], group[adj]); // update group.
+ if (group[adj]>= depth + 1) critical.push([node, adj]);
+ }
+ };
+
+ dfs(0);
+ return critical;
+ // end::solution[]
+ // tag::description[]
+}
+// end::description[]
+
+// tag::criticalConnectionsBrute1[]
+function areAllNodesReachable(n, graph) {
+ const seen = Array(n).fill(false);
+ const queue = new Queue([0]);
+
+ while (queue.size) {
+ const node = queue.dequeue();
+ if (seen[node]) continue;
+ seen[node] = true;
+
+ for (const adj of (graph.get(node) || [])) {
+ queue.enqueue(adj);
+ }
+ }
+
+ return !seen.some((s) => !s);
+}
+
+function criticalConnectionsBrute1(n, connections) {
+ const critical = [];
+ const graph = new Map(Array(n).fill(0).map((_, i) => [i, []]));
+ connections.forEach(([u, v]) => {
+ graph.get(u).push(v);
+ graph.get(v).push(u);
+ });
+
+ for (const [u, v] of connections) {
+ // remove edge
+ graph.set(u, (graph.get(u) || []).filter((e) => e !== v));
+ graph.set(v, (graph.get(v) || []).filter((e) => e !== u));
+
+ if (!areAllNodesReachable(n, graph)) critical.push([u, v]);
+
+ // add it back
+ graph.get(u).push(v);
+ graph.get(v).push(u);
+ }
+
+ return critical;
+}
+// end::criticalConnectionsBrute1[]
+
+module.exports = { criticalConnections, criticalConnectionsBrute1 };
diff --git a/book/interview-questions/critical-connections-in-a-network.spec.js b/book/interview-questions/critical-connections-in-a-network.spec.js
new file mode 100644
index 00000000..003374d7
--- /dev/null
+++ b/book/interview-questions/critical-connections-in-a-network.spec.js
@@ -0,0 +1,37 @@
+const { criticalConnections, criticalConnectionsBrute1 } = require('./critical-connections-in-a-network');
+// const { } = require('../../src/index');
+
+[criticalConnections, criticalConnectionsBrute1].forEach((fn) => {
+ describe(`Graph: ${fn.name}`, () => {
+ it('should work with null/empty', () => {
+ const actual = fn(0, []);
+ const expected = [];
+ expect(actual).toEqual(expected);
+ });
+
+ it('should work with critical path', () => {
+ const actual = fn(4, [[0, 1], [1, 2], [2, 0], [1, 3]]);
+ const expected = [[1, 3]];
+ expect(actual).toEqual(expected);
+ });
+
+ it('should work without critical path', () => {
+ const actual = fn(4, [[0, 1], [1, 2], [2, 0], [1, 3], [3, 2]]);
+ const expected = [];
+ expect(actual).toEqual(expected);
+ });
+
+ it('should work with other case', () => {
+ const actual = fn(3, [[0, 1], [1, 2]]);
+ const expected = [[0, 1], [1, 2]];
+ expect(actual).toEqual(expect.arrayContaining(expected));
+ });
+
+
+ it('should work with 2 SCC', () => {
+ const actual = fn(6, [[0, 1], [1, 2], [2, 0], [1, 3], [3, 4], [4, 5], [5, 3]]);
+ const expected = [[1, 3]];
+ expect(actual).toEqual(expected);
+ });
+ });
+});
diff --git a/book/interview-questions/network-delay-time.js b/book/interview-questions/network-delay-time.js
new file mode 100644
index 00000000..99f8c108
--- /dev/null
+++ b/book/interview-questions/network-delay-time.js
@@ -0,0 +1,28 @@
+// https://leetcode.com/problems/network-delay-time/solution/
+function networkDelayTime(times: number[][], N: number, K: number): number {
+ const graph = new Map(Array(N).fill(0).map((_, i) => [i + 1, []]));
+ times.forEach(([u, v, w]) => graph.get(u)?.push([v, w]));
+
+ const queue = new Queue([[K, 0]]);
+ const seen = Array(N + 1).fill(Infinity);
+
+ while (queue.size()) {
+ const [node, dist] = queue.dequeue();
+ seen[node] = Math.min(seen[node], dist);
+
+ for (const [adj, w] of graph.get(node) || []) {
+ if (seen[adj]> dist + w) queue.enqueue([adj, dist + w]);
+ }
+ }
+
+ const max = Math.max(...seen.slice(1));
+ return max === Infinity ? -1 : max;
+};
+
+/*
+[[2,1,1],[2,3,1],[3,4,1]]
+4
+2
+
+
+*/
diff --git a/book/interview-questions/network-delay-time.spec.js b/book/interview-questions/network-delay-time.spec.js
new file mode 100644
index 00000000..c56ff203
--- /dev/null
+++ b/book/interview-questions/network-delay-time.spec.js
@@ -0,0 +1,5 @@
+describe('', () => {
+ it('', () => {
+
+ });
+});
diff --git a/package-lock.json b/package-lock.json
index 7d5d9509..bf7a41de 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "dsa.js",
- "version": "1.17.0",
+ "version": "1.18.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/package.json b/package.json
index feaa51db..5e130a84 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "dsa.js",
- "version": "1.17.0",
+ "version": "1.18.0",
"description": "Data Structures & Algorithms in JS",
"author": "Adrian Mejia (https://adrianmejia.com)",
"homepage": "https://github.com/amejiarosario/dsa.js",