Skip to content
Merged
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
45 changes: 23 additions & 22 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,31 @@
* Graphs
* Course Schedule
* [Test Course Schedule](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/course_schedule/test_course_schedule.py)
* Evaluate Division
* [Test Evaluate Division](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/evaluate_division/test_evaluate_division.py)
* Frog Position After T Seconds
* [Test Frog Position After T Seconds](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/frog_position_after_t_seconds/test_frog_position_after_t_seconds.py)
* Keys And Rooms
* [Test Can Visit All Rooms](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/keys_and_rooms/test_can_visit_all_rooms.py)
* Knight On Chess Board
* [Test Knight On Chess Board](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/knight_on_chess_board/test_knight_on_chess_board.py)
* Maxareaofisland
* [Test Max Area Of Island](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/maxareaofisland/test_max_area_of_island.py)
* Min Cost To Supply
* [Test Min Cost To Supply](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/min_cost_to_supply/test_min_cost_to_supply.py)
* Nearest Exit From Entrance In Maze
* [Test Nearest Exit From Entrance](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/nearest_exit_from_entrance_in_maze/test_nearest_exit_from_entrance.py)
* Number Of Islands
* [Test Number Of Islands](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/number_of_islands/test_number_of_islands.py)
* [Union Find](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/number_of_islands/union_find.py)
* Number Of Provinces
* [Test Number Of Provinces](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/number_of_provinces/test_number_of_provinces.py)
* Reorder Routes
* [Test Reorder Routes](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/reorder_routes/test_reorder_routes.py)
* Rotting Oranges
* [Test Rotting Oranges](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/rotting_oranges/test_rotting_oranges.py)
* Valid Path
* [Test Valid Path](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/valid_path/test_valid_path.py)
* Greedy
* Boats
* [Test Boats To Save People](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/greedy/boats/test_boats_to_save_people.py)
Expand Down Expand Up @@ -583,28 +606,6 @@
* [Test Climb Stairs](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/climb_stairs/test_climb_stairs.py)
* Find Missing Elem
* [Test Find Missing Elem](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/find_missing_elem/test_find_missing_elem.py)
* Graphs
* Evaluate Division
* [Test Evaluate Division](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/graphs/evaluate_division/test_evaluate_division.py)
* Keys And Rooms
* [Test Can Visit All Rooms](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/graphs/keys_and_rooms/test_can_visit_all_rooms.py)
* Knight On Chess Board
* [Test Knight On Chess Board](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/graphs/knight_on_chess_board/test_knight_on_chess_board.py)
* Maxareaofisland
* [Test Max Area Of Island](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/graphs/maxareaofisland/test_max_area_of_island.py)
* Nearest Exit From Entrance In Maze
* [Test Nearest Exit From Entrance](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/graphs/nearest_exit_from_entrance_in_maze/test_nearest_exit_from_entrance.py)
* Number Of Islands
* [Test Number Of Islands](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/graphs/number_of_islands/test_number_of_islands.py)
* [Union Find](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/graphs/number_of_islands/union_find.py)
* Number Of Provinces
* [Test Number Of Provinces](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/graphs/number_of_provinces/test_number_of_provinces.py)
* Reorder Routes
* [Test Reorder Routes](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/graphs/reorder_routes/test_reorder_routes.py)
* Rotting Oranges
* [Test Rotting Oranges](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/graphs/rotting_oranges/test_rotting_oranges.py)
* Valid Path
* [Test Valid Path](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/graphs/valid_path/test_valid_path.py)
* Hashmap
* Close Strings
* [Test Close Strings](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/hashmap/close_strings/test_close_strings.py)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Given any source point, (C, D) and destination point, (E, F) on a chess board, we need to find whether Knight can move
to the destination or not.

![knight_on_chess_board](./knight_on_chess_board.jpg)
![knight_on_chess_board](knight_on_chess_board.jpg)

The above figure details the movements for a knight ( 8 possibilities ).

Expand Down
91 changes: 91 additions & 0 deletions algorithms/graphs/min_cost_to_supply/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Optimize Water Distribution in a Village

There are n houses in a village, and we want to supply water to every house. To do this, we have two options:
1. Build a well directly at a house: The costs for building wells are given in the wells array, where wells[i - 1]
represents the cost to build a well at house i (houses are numbered from 1 to n, but the array is 0-indexed).
2. Lay water pipes to connect two houses: Allowing one to receive water from the other. The costs for building these
connections are given in the pipes array. Each pipe entry is written as [house1, house2, cost], meaning it costs to
construct a pipe connecting house1 and house2. The connection is two-way (water can flow in both directions).

In the given data, multiple pipe entries may connect the same pair of houses, but with different costs. Each entry
represents an available option; you may choose the most cost-effective one.

Your task is to determine the minimum total cost to ensure every house gets access to water, either by building a well
directly at it or connecting it via pipes to another house with access to water.

## Constraints

- 2 <= n <= 10^4
- `wells.length` == n
- 0 <= `wells[i]` <= 10^5
- 1 <= `pipes.length` <= 10^4
- `pipes[j].length` == 3
- 1 <= `house1j`, `house2j` <= n
- 0 <= `costj` <= 10^5
- `house1j` != `house2j`

## Examples

![Example 1](./images/examples/min_cost_to_supply_example_1.png)
![Example 2](./images/examples/min_cost_to_supply_example_2.png)
![Example 3](./images/examples/min_cost_to_supply_example_3.png)
![Example 4](./images/examples/min_cost_to_supply_example_4.png)
![Example 5](./images/examples/min_cost_to_supply_example_5.png)

## Solution

This problem can be viewed as a graph connectivity problem where each house represents a node, edges represent pipes
between houses, or the option to build a well. We use the Union-Find (Disjoint Set Union) data structure to construct a
minimal connection network.

We begin with an extra virtual node (node 0), representing a central water source. For every house i, we add an edge
from the virtual node to that house with a cost equal to wells[i - 1], representing the option of building a well. All
the original pipe connections are included as regular edges between houses with their given costs.

Next, we sort all these edges (pipes and virtual edges) by cost in ascending order. This enables a greedy approach where
we always consider the cheapest connection available at each step. We initialize each node (including the virtual node)
in its own set using Union-Find. Then, we iterate through the sorted list of edges. For each edge, we check if the two
endpoints belong to different sets (i.e., they are not yet connected). If they are in different sets, we perform a union
operation to connect them and add the edge’s cost to the total cost.

We continue this process until all houses are connected either directly to the water source or indirectly via pipes to
other connected houses. The total cost accumulated during these union operations gives us the minimum cost to supply water
to all houses.

Here’s the step-by-step explanation of the solution:

1. Create a Union-Find (Disjoint Set Union) structure with n + 1 elements.
2. For each house i from 1 to n, add an edge [0, i, wells[i - 1]] to the edges list.
3. Add all entries from pipes to the edges list.
4. Sort the edges list in ascending order by cost.
5. Initialize totalCost = 0.
6. For each edge [house1, house2, cost] in the sorted list:
- If find(house1) != find(house2):
- Perform union(house1, house2).
- Add cost to totalCost.
7. Return totalCost.

Let’s look at the following illustration to get a better understanding of the solution:

![Solution 1](./images/solutions/min_cost_to_supply_solution_1.png)
![Solution 2](./images/solutions/min_cost_to_supply_solution_2.png)
![Solution 3](./images/solutions/min_cost_to_supply_solution_3.png)
![Solution 4](./images/solutions/min_cost_to_supply_solution_4.png)
![Solution 5](./images/solutions/min_cost_to_supply_solution_5.png)
![Solution 6](./images/solutions/min_cost_to_supply_solution_6.png)
![Solution 7](./images/solutions/min_cost_to_supply_solution_7.png)
![Solution 8](./images/solutions/min_cost_to_supply_solution_8.png)
![Solution 9](./images/solutions/min_cost_to_supply_solution_9.png)

### Time Complexity

We process n wells and m pipes, resulting in an total of E=n+m edges.
- Sorting all edges takes O(E logE) time.
- Each find and union operation in Union-Find takes O(α(n)), leading to a total of O(E*α(n)) for all operations.

As α(n) is nearly constant, the dominant term is the sorting step. Therefore, the overall time complexity is O((n+m)*log(n+m)).

### Space Complexity

We use a Union-Find data structure with n+1 elements, requiring O(n) space for the parent array. The edge list contains
n well edges and m pipe edges, totaling O(n+m) space. Therefore, the overall space complexity is O(n+m).
51 changes: 51 additions & 0 deletions algorithms/graphs/min_cost_to_supply/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from typing import List
from datastructures.sets.union_find import UnionFind


def min_cost_to_supply_water(n: int, wells: List[int], pipes: List[List[int]]) -> int:
# Create a list of all edges including virtual source edges
edges = []

# Add edges from virtual source (node 0) to each house with well costs
# This represents the option to build a well at each house
for i in range(n):
edges.append((0, i + 1, wells[i]))

# Add all pipe connections between houses
for house1, house2, cost in pipes:
edges.append((house1, house2, cost))

# Sort all edges by cost (key step in Kruskal's algorithm)
edges.sort(key=lambda x: x[2])

# Initialize Union-Find with n+1 nodes (node 0 through n)
# Node 0 is our virtual water source
uf = UnionFind(n + 1)

total_cost = 0
edges_used = 0

# Greedily select edges in order of increasing cost
for house1, house2, cost in edges:
# Only add this edge if it connects two different components
if uf.union(house1, house2):
total_cost += cost
edges_used += 1
# We need exactly n edges to connect n+1 nodes into a tree
if edges_used == n:
break

return total_cost


def min_cost_to_supply_water_2(n: int, wells: List[int], pipes: List[List[int]]) -> int:
edges = [[0, i + 1, wells[i]] for i in range(n)] + pipes
edges.sort(key=lambda x: x[2])
uf = UnionFind(n + 1)
total_cost = 0

for house1, house2, cost in edges:
if uf.union(house1, house2):
total_cost += cost

return total_cost
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions algorithms/graphs/min_cost_to_supply/test_min_cost_to_supply.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import unittest
from typing import List
from parameterized import parameterized
from algorithms.graphs.min_cost_to_supply import (
min_cost_to_supply_water,
min_cost_to_supply_water_2,
)

MIN_COST_TO_SUPPLY_WATER_TEST_CASES = [
(2, [3, 4], [[1, 2, 2]], 5),
(4, [1, 1, 1, 1], [[1, 2, 1], [2, 3, 1], [3, 4, 1]], 4),
(3, [10, 10, 1], [[1, 2, 1], [2, 3, 1]], 3),
(4, [5, 5, 5, 5], [[1, 2, 2], [1, 3, 2], [1, 4, 2]], 11),
(3, [3, 4, 5], [[1, 2, 5], [2, 3, 6], [1, 3, 7]], 12),
(3, [8, 2, 3], [[1, 2, 1], [2, 3, 1]], 4),
(5, [5, 3, 4, 2, 1], [[1, 2, 1], [2, 3, 1], [3, 4, 1], [4, 5, 1]], 5),
(6, [1, 2, 3, 4, 5, 6], [[1, 2, 1], [2, 3, 1], [3, 4, 1], [4, 5, 1], [5, 6, 1]], 6),
]


class MinCostToSupplyWaterTestCase(unittest.TestCase):
@parameterized.expand(MIN_COST_TO_SUPPLY_WATER_TEST_CASES)
def test_min_cost_to_supply_water(
self, n: int, wells: List[int], pipes: List[List[int]], expected: int
):
actual = min_cost_to_supply_water(n, wells, pipes)
self.assertEqual(expected, actual)

@parameterized.expand(MIN_COST_TO_SUPPLY_WATER_TEST_CASES)
def test_min_cost_to_supply_water_2(
self, n: int, wells: List[int], pipes: List[List[int]], expected: int
):
actual = min_cost_to_supply_water_2(n, wells, pipes)
self.assertEqual(expected, actual)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Return the number of steps in the shortest path from the entrance to the nearest

Example 1:

![nearest-grid-1](./nearest1-grid.jpg)
![nearest-grid-1](nearest1-grid.jpg)

```plain
Input: maze = [["+","+",".","+"],[".",".",".","+"],["+","+","+","."]], entrance = [1,2]
Expand All @@ -26,7 +26,7 @@ Thus, the nearest exit is [0,2], which is 1 step away.
```

Example 2:
![nearest-grid-2](./nearest2-grid.jpg)
![nearest-grid-2](nearest2-grid.jpg)

```plain
Input: maze = [["+","+","+"],[".",".","."],["+","+","+"]], entrance = [1,0]
Expand All @@ -39,7 +39,7 @@ Thus, the nearest exit is [1,2], which is 2 steps away.
```

Example 3:
![nearest-grid-3](./nearest3-grid.jpg)
![nearest-grid-3](nearest3-grid.jpg)

```plain
Input: maze = [[".","+"]], entrance = [0,0]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import List, Tuple, Set
from puzzles.graphs.number_of_islands.union_find import UnionFind
from algorithms.graphs.number_of_islands.union_find import UnionFind


def num_of_islands(grid: List[List[str]]) -> int:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import List
import unittest
from parameterized import parameterized
from puzzles.graphs.number_of_islands import num_of_islands, num_islands_union_find
from algorithms.graphs.number_of_islands import num_of_islands, num_islands_union_find


class NumberOfIslandsTestCase(unittest.TestCase):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ It's guaranteed that each city can reach city 0 after reorder.

Example 1

![](./reorder_routes_example_1.png)
![](reorder_routes_example_1.png)

Input: n = 6, connections = [[0,1],[1,3],[2,3],[4,0],[4,5]]
Output: 3
Explanation: Change the direction of edges show in red such that each node can reach the node 0 (capital).

Example 2

![](./reorder_routes_example_2.png)
![](reorder_routes_example_2.png)

Input: n = 5, connections = [[1,0],[1,2],[3,2],[3,4]]
Output: 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Return the minimum number of minutes that must elapse until no cell has a fresh

Example 1

![Rotting Oranges](./rotting_oranges.png)
![Rotting Oranges](rotting_oranges.png)

```plain

Expand Down
Empty file removed puzzles/graphs/__init__.py
Empty file.
Loading