Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 74 additions & 65 deletions 05/implement-network-delay.checkpoint.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,49 +8,63 @@
* title: Network Delay Time
* points: 3
### !question

For this exercise, create a function `network_delay_time` which accepts the following parameters:
- A list of travel times, `times`. Each element of `times` represents a directed edge `times[i] = (uᵢ, vᵢ, wᵢ)` where `uᵢ` is the source node, `vᵢ` is the target node, and `wᵢ` is the time it takes for a signal to travel from the source node to the target node
- A list of travel times, `times`. Each element of `times` represents a directed edge `times[i] = (uᵢ, vᵢ, wᵢ)` where:
- `uᵢ` is the source node
- `vᵢ` is the target node
- `wᵢ` is the time it takes for a signal to travel from the source node to the target node
- The total number of nodes in the graph, `n`
- The node from which a signal is being sent, `source`

The nodes in the graph are labeled from `1` to `n`.

Return the **minimum** time it takes for **all** of the nodes in the graph to receive the signal from the `source` node. If it is not possible for all of the nodes to receive the signal, return `-1`.

**Example 1**
<br>
### Example 1

![example graph 1](../images/network_delay_example-1.png)
```
Input: times = [[2,1,1], [2,3,1], [3,4,1]], source = 2, n = 4

```py
Inputs: times = [[2,1,1], [2,3,1], [3,4,1]], n = 4, source = 2

Output: 2
Explanation:
Starting from node 2 (our source node): it takes 1 unit of time to reach node 1, 1 unit of time to
reach node 3, and 2 units of time to reach node 4 (1 unit of time from 2 -> 3 and
1 unit of time from 3 -> 4 so 2 units overall).
Therefore, to reach all of the nodes, it would take a minimum of 2 units of time.
```

**Example 2:**
<br>
![example graph 2](../images/network_delay_example-2.png)
```
Input: times =[[2,1,1], [2, 3, 2], [3, 1, 1]], source = 1, n = 3
**Explanation:** Starting from node 2: it takes 1 unit of time to reach node 1, 1 unit of
time to reach node 3, and 2 units of time to reach node 4 (1 unit of time from
2 -> 3 and 1 unit of time from 3 -> 4 so 2 units overall).

Therefore, to reach all of the nodes, it would take a minimum of 2 units of time.

### Example 2

![example graph 2](../images/network_delay_example-2.png)

```py
Inputs: times = [[2,1,1], [2, 3, 2], [3, 1, 1]], n = 3, source = 1

Output: -1
It is not possible to reach any other node from node 1, so the function would return
-1 to indicate it is not possible to reach all of the nodes in the graph from the given
source node.
```

**Example 3:**
<br>
**Explanation:** It is not possible to reach any other node from node 1, so the function would
return -1 to indicate it is not possible to reach all of the nodes in the graph
from the given source node.

### Example 3

![example graph 3](../images/network_delay_example-3.png)
```
Input: times =[[2, 3, 2]], source = 2, n = 3

```py
Input: times = [[2, 3, 2]], n = 3, source = 2

Output: -1
It is not possible to reach all of the nodes in the graph due to the graph being
```

**Explanation:** It is not possible to reach all of the nodes in the graph due to the graph being
disconnected, so the function would return -1 to indicate it is not possible to
reach all of the nodes in the graph from the given source node.
```

### !end-question
### !placeholder

Expand All @@ -77,6 +91,7 @@ def network_delay_time(times, n, source):

### !end-placeholder
### !tests

```python
import unittest
from main import *
Expand All @@ -93,10 +108,10 @@ class TestChallenge(unittest.TestCase):
source = 2

# Act
answer = network_delay_time(times, n, source)
result = network_delay_time(times, n, source)

# Assert
self.assertEqual(answer, 2)
self.assertEqual(result, 2)

def test_network_delay_returns_minus_1_when_node_unreachable(self):
# Arrange
Expand All @@ -109,10 +124,10 @@ class TestChallenge(unittest.TestCase):
source = 1

# Act
answer = network_delay_time(times, n, source)
result = network_delay_time(times, n, source)

# Assert
self.assertEqual(answer, -1)
self.assertEqual(result, -1)

def test_network_delay_returns_minus_1_for_disconnected_graph(self):
# Arrange
Expand All @@ -123,10 +138,10 @@ class TestChallenge(unittest.TestCase):
source = 2

# Act
answer = network_delay_time(times, n, source)
result = network_delay_time(times, n, source)

# Assert
self.assertEqual(answer, -1)
self.assertEqual(result, -1)

def test_network_delay_returns_correct_result_for_larger_graph(self):
# Arrange
Expand All @@ -142,11 +157,12 @@ class TestChallenge(unittest.TestCase):
source = 1

# Act
answer = network_delay_time(times, n, source)
result = network_delay_time(times, n, source)

# Assert
self.assertEqual(answer, 9)
self.assertEqual(result, 9)
```

### !end-tests
### !explanation

Expand All @@ -155,6 +171,7 @@ An example of a working implementation:
```python
import collections
from heapq import heappush, heappop

def network_delay_time(times, n, source):
# Initialize dictionary with default value as a list
# More information about defaultdict can be found in the documentation for Python: https://docs.python.org/3/library/collections.html#collections.defaultdict
Expand Down Expand Up @@ -202,7 +219,6 @@ def network_delay_time(times, n, source):
return max(time_needed) if len(visited) == n else -1
```
### !end-explanation

### !end-challenge
<!-- prettier-ignore-end -->

Expand All @@ -221,10 +237,11 @@ def test_network_delay_returns_correct_result_for_small_connected_graph():
source = 2

# Act
answer = network_delay_time(times, n, source)
result = network_delay_time(times, n, source)

# Assert
assert answer == 2
assert result == 2


def test_network_delay_returns_minus_1_when_node_unreachable():
# Arrange
Expand All @@ -237,10 +254,11 @@ def test_network_delay_returns_minus_1_when_node_unreachable():
source = 1

# Act
answer = network_delay_time(times, n, source)
result = network_delay_time(times, n, source)

# Assert
assert answer == -1
assert result == -1


def test_network_delay_returns_minus_1_for_disconnected_graph():
# Arrange
Expand All @@ -251,10 +269,11 @@ def test_network_delay_returns_minus_1_for_disconnected_graph():
source = 2

# Act
answer = network_delay_time(times, n, source)
result = network_delay_time(times, n, source)

# Assert
assert answer == -1
assert result == -1


def test_network_delay_returns_correct_result_for_larger_graph():
# Arrange
Expand All @@ -270,45 +289,42 @@ def test_network_delay_returns_correct_result_for_larger_graph():
source = 1

# Act
answer = network_delay_time(times, n, source)
result = network_delay_time(times, n, source)

# Assert
assert answer == 9
assert result == 9
```

</details>

<!-- >>>>>>>>>>>>>>>>>>>>>> BEGIN CHALLENGE >>>>>>>>>>>>>>>>>>>>>> -->
<!-- Replace everything in square brackets [] and remove brackets -->

<!-- prettier-ignore-start -->
### !challenge

* type: paragraph
* id: 5d1ff7ab-da11-4d64-95a9-3b07c55a9b2c
* title: Time Complexity of Solution
* points: 1

##### !question

What is the time complexity of your solution? Please define and explain your variables.

##### !end-question

##### !placeholder

##### !end-placeholder

##### !hint

Check the next hint for some points to keep in mind that might impact the time complexity of the sample implementation.
##### !end-hint

##### !end-hint
##### !hint

The sample implementation is based on Dijkstra's algorithm, so that will be a major driver of the time complexity. Additionally, we first translate from the edge list representation to an adjacency dict representation. And finally, we need to find the maximum delay time in the list of times for each node.

The next hint presents a discussion of the time complexity of the sample solution.
##### !end-hint

##### !end-hint
##### !hint

Converting the edge list to an adjacency dict requires that we iterate over each of the edges in the list. Adding each entry is constant time (both to make the initial node entry with an empty list, and for adding each edge to the list of edges for the node). This is O(E) time, where E is the number of edges in the graph.

In preparing to run Dijkstra's algorithm, we set up a list to hold the delay times. This is O(N) time, where N is the number of nodes in the graph, since it take O(N) time to initialize a list of length N. The remaining setup is all constant time.
Expand All @@ -322,42 +338,37 @@ Finally, after calculating all the delays, we must locate the maximum value in t
Summing up all the parts, we have changing the representation O(E) + initializing data for Dijkstra O(N) + the main loop O(E log E) + finding the maximum O(N) for a total time complexity of O(E + 2N + E log E). While we can't drop the N term (since we don't know the relationship between N and E), we _can_ drop the solitary E term, since O(E log E) will dominate, as well as the coefficient on N, for a final complexity of O(N + E log E), which agrees with our general analysis of using Dijkstra's algorithm on an adjacency list or dictionary.

##### !end-hint

### !end-challenge
<!-- prettier-ignore-end -->

<!-- ======================= END CHALLENGE ======================= -->

<!-- >>>>>>>>>>>>>>>>>>>>>> BEGIN CHALLENGE >>>>>>>>>>>>>>>>>>>>>> -->
<!-- Replace everything in square brackets [] and remove brackets -->

<!-- prettier-ignore-start -->
### !challenge

* type: paragraph
* id: ae994d0b-c54a-45e0-8793-9c0ceee33718
* title: Space Complexity of Solution
* points: 1

##### !question

What is the space complexity of your solution? Please define and explain your variables.

##### !end-question

##### !placeholder

##### !end-placeholder

##### !hint

Check the next hint for some points to keep in mind that might impact the space complexity of the sample implementation.
##### !end-hint

##### !end-hint
##### !hint

The sample implementation is based on Dijkstra's algorithm, so that will be a major driver of the space complexity. Additionally, we first translate from the edge list representation to an adjacency dict representation, so we'll need to account for that as well.

The next hint presents a discussion of the space complexity of the sample solution.
##### !end-hint

##### !end-hint
##### !hint

We incur no complexity cost for any of the data passed in (the edge list or the other scalar values). The first thing we do that _does_ incur cost is convert the list of edges into an adjacency dict. This requires that we create a new dictionary, and then iterate over each of the edges in the list, adding each to the dictionary. This is O(E) space, where E is the number of edges in the graph. We may not know how many nodes we will represent in the dictionary, but under the ones we do know about, we will have at most E entries in total.

Note that due to our use of a `defaultdict`, even if not all the nodes have outgoing edges, it's possible, while our logic runs, that it will still attempt to lookup and iterate over the edges of such nodes. This would cause keys for those nodes to be added to the dictionary, even though they had no edge data. Thus, we _could_ say that as written, the size of the graph could eventually grow to O(E + N). We'll ignore this for the moment, as we could add an explicit `in` check to our logic to prevent this from happening, and regardless, we'll see this doesn't affect the space complexity in the end.
Expand All @@ -369,7 +380,5 @@ While the main logic of Dijkstra's algorithm runs, we only add nodes to the visi
This accounts for all of the space utilization, since finding the maximum at the end incurs no additional space costs. Summing up all the parts, we have changing the representation O(E) + initializing data for Dijkstra O(N) + the growth of `visited` O(N) + the growth of `heap` O(E) for a total time complexity of O(2E + 2N). Dropping coefficients leaves us with a final complexity of O(N + E), which agrees with our general space analysis for using Dijkstra's algorithm in the absence of `decrease_key`. Even without that caveat, our space complexity would still be O(N + E) since we do create a new dictionary to hold the adjacency list representation of the graph. Also notice that even had we considered the complexity of the graph to be O(N + E), the final complexity would still be O(N + E), since this would only have affected the coefficient on N, which we drop anyway!

##### !end-hint

### !end-challenge

<!-- ======================= END CHALLENGE ======================= -->
<!-- prettier-ignore-end -->
Loading