diff --git a/DIRECTORY.md b/DIRECTORY.md index 70c4d43f..797ce5f7 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -83,6 +83,8 @@ * 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) * Greedy + * Boats + * [Test Boats To Save People](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/greedy/boats/test_boats_to_save_people.py) * Gas Stations * [Test Gas Stations](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/greedy/gas_stations/test_gas_stations.py) * Jump Game @@ -91,6 +93,8 @@ * [Test Majority Element](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/greedy/majority_element/test_majority_element.py) * Min Arrows * [Test Find Min Arrows](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/greedy/min_arrows/test_find_min_arrows.py) + * Spread Stones + * [Test Minimum Moves To Spread Stones](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/greedy/spread_stones/test_minimum_moves_to_spread_stones.py) * Huffman * [Decoding](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/huffman/decoding.py) * [Encoding](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/huffman/encoding.py) diff --git a/algorithms/greedy/boats/README.md b/algorithms/greedy/boats/README.md new file mode 100644 index 00000000..d5395e8f --- /dev/null +++ b/algorithms/greedy/boats/README.md @@ -0,0 +1,97 @@ +# Boats to save people + +A big ship with numerous passengers is sinking, and there is a need to evacuate these people with the minimum number of +life-saving boats. Each boat can carry, at most, two persons however, the weight of the people cannot exceed the +carrying weight limit of the boat. + +We are given an array, people, where people[i] is the weight of the `ith` person, and an infinite number of boats, where +each boat can carry a maximum weight, limit. Each boat carries, at most, two people at the same time. This is provided +that the sum of the weight of these people is under or equal to the weight limit. + +You need to return the minimum number of boats to carry all persons in the array. + +## Constraints + +- 1 <= `people.length` <= 5 * 10^3 +- 1 <= `people[i]` <= `limit` <= 3 * 10^3 + +## Examples + +![Example 1](./images/examples/boats_to_save_people_example_1.png) +![Example 2](./images/examples/boats_to_save_people_example_2.png) +![Example 3](./images/examples/boats_to_save_people_example_3.png) +![Example 4](./images/examples/boats_to_save_people_example_4.png) +![Example 5](./images/examples/boats_to_save_people_example_5.png) + +## Solution + +1. [Naive Approach](#naive-approach) +2. [Optimized Approach Using Greedy Pattern](#greedy-pattern) + +### Naive Approach + +The naive approach is to use a nested loop. For each person, we can check all the remaining people to see if they can +form a pair that fits into a boat. If we find a pair, we’ll remove them from the array, increment the number of boats +used, and move to the next person. If we can’t find a pair for a person, we put them in a boat alone and increment the +number of boats used. We repeat this process until all people are rescued. + +The time complexity of this approach is O(n^2), since we’ll use the nested loop to make pairs. + +### Greedy Pattern + +To solve the problem, we can use the greedy pattern and pair people with the lightest and heaviest people available, as +long as their combined weight does not exceed the weight limit. If the combined weight exceeds the limit, we can only +send one person on that boat. This approach ensures that we use the minimum number of boats to rescue the people. + +The steps to implement the approach above are given below: + +1. Sort the people array in ascending order so that the lightest person is at the start of the array, and the heaviest + person is at the end. + +2. Initialize two pointers, left and right. The left pointer points to the lightest person at the start of the array, + and the right pointer points to the heaviest person at the end of the array. Next, a variable, boats, is initialized + to 0, representing the number of boats used. + +3. Iterate over the people array until the left pointer is greater than the right pointer. This means that all people + have been rescued. Perform the following steps in each iteration of the loop + - Check if both the lightest and heaviest persons can fit in one boat, i.e., people[left] + people[right] is less + than or equal to limit. If they can fit, the left pointer is incremented and the right pointer is decremented. + - If they cannot fit in one boat, the heaviest person is rescued alone, and the right pointer is decremented. + - The boats variable is incremented by 1, representing the number of boats used. + +4. Return the minimum number of boats required to rescue all the people. + +![Solution 1](./images/solutions/boats_to_save_people_solution_1.png) +![Solution 2](./images/solutions/boats_to_save_people_solution_2.png) +![Solution 3](./images/solutions/boats_to_save_people_solution_3.png) +![Solution 4](./images/solutions/boats_to_save_people_solution_4.png) +![Solution 5](./images/solutions/boats_to_save_people_solution_5.png) +![Solution 6](./images/solutions/boats_to_save_people_solution_6.png) +![Solution 7](./images/solutions/boats_to_save_people_solution_7.png) +![Solution 8](./images/solutions/boats_to_save_people_solution_8.png) +![Solution 9](./images/solutions/boats_to_save_people_solution_9.png) +![Solution 10](./images/solutions/boats_to_save_people_solution_10.png) + +#### Solution Summary + +- Sort the people array. +- Initialize two pointers—left at the start and right at the end of the array. +- Iterate over the people array while the left pointer is less than or equal to the right pointer. + - Check if both the lightest and heaviest persons can fit in one boat. If so, increment the left pointer and decrement + the right pointer. + - Otherwise, rescue the heaviest person alone and decrement the right pointer. + - Increment the boats after each rescue operation. + +#### Time Complexity + +The time complexity for the solution is O(n log n), since sorting the people array takes O(n log n) time. + +#### Space Complexity + +The sorting algorithm takes O(n) space to sort the people array. Therefore, the space complexity of the solution above +is O(n). + +## Topics + +- Greedy +- Two Pointers diff --git a/algorithms/greedy/boats/__init__.py b/algorithms/greedy/boats/__init__.py new file mode 100644 index 00000000..90b5d24a --- /dev/null +++ b/algorithms/greedy/boats/__init__.py @@ -0,0 +1,41 @@ +from typing import List + + +def rescue_boats(people: List[int], limit: int) -> int: + """ + Finds the minimum number of rescue boats that can be used to save people from a sinking ship. Note that the assumption + made here is that the number of boats is unlimited + Args: + people (list): list of weights of people + limit (int): weight limit of each boat + Returns: + int: minimum number of rescue boats required + """ + # copy over the people list to avoid mutating the input list + weights = people[:] + # sort the list of weights + weights.sort() + + # using two pointers to move along the weights to be able to track pairs of people, the left pointer will be at 0 + # initially, i.e. at the lighter person and the right pointer will be at the end of the list, which will be the heavier + # person + left_pointer, right_pointer = 0, len(weights) - 1 + + # this keeps track of the total rescue boats that will be used + boats = 0 + + while left_pointer <= right_pointer: + lightest = weights[left_pointer] + heaviest = weights[right_pointer] + current_weight = lightest + heaviest + # If the current weight is less than the limit of a single boat + if current_weight <= limit: + # move to the next heavier person and back down the heavier scale + left_pointer += 1 + # the heavier person boards the boat and we move the right pointer + right_pointer -= 1 + + # either way, we increment the number of boats + boats += 1 + + return boats diff --git a/algorithms/greedy/boats/images/examples/boats_to_save_people_example_1.png b/algorithms/greedy/boats/images/examples/boats_to_save_people_example_1.png new file mode 100644 index 00000000..9d1e1408 Binary files /dev/null and b/algorithms/greedy/boats/images/examples/boats_to_save_people_example_1.png differ diff --git a/algorithms/greedy/boats/images/examples/boats_to_save_people_example_2.png b/algorithms/greedy/boats/images/examples/boats_to_save_people_example_2.png new file mode 100644 index 00000000..a82613f7 Binary files /dev/null and b/algorithms/greedy/boats/images/examples/boats_to_save_people_example_2.png differ diff --git a/algorithms/greedy/boats/images/examples/boats_to_save_people_example_3.png b/algorithms/greedy/boats/images/examples/boats_to_save_people_example_3.png new file mode 100644 index 00000000..ded6c7cb Binary files /dev/null and b/algorithms/greedy/boats/images/examples/boats_to_save_people_example_3.png differ diff --git a/algorithms/greedy/boats/images/examples/boats_to_save_people_example_4.png b/algorithms/greedy/boats/images/examples/boats_to_save_people_example_4.png new file mode 100644 index 00000000..b2477491 Binary files /dev/null and b/algorithms/greedy/boats/images/examples/boats_to_save_people_example_4.png differ diff --git a/algorithms/greedy/boats/images/examples/boats_to_save_people_example_5.png b/algorithms/greedy/boats/images/examples/boats_to_save_people_example_5.png new file mode 100644 index 00000000..aa3f67f7 Binary files /dev/null and b/algorithms/greedy/boats/images/examples/boats_to_save_people_example_5.png differ diff --git a/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_1.png b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_1.png new file mode 100644 index 00000000..c46d8471 Binary files /dev/null and b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_1.png differ diff --git a/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_10.png b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_10.png new file mode 100644 index 00000000..c649a26d Binary files /dev/null and b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_10.png differ diff --git a/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_2.png b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_2.png new file mode 100644 index 00000000..be608ac1 Binary files /dev/null and b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_2.png differ diff --git a/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_3.png b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_3.png new file mode 100644 index 00000000..8c4e5553 Binary files /dev/null and b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_3.png differ diff --git a/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_4.png b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_4.png new file mode 100644 index 00000000..fd1f997c Binary files /dev/null and b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_4.png differ diff --git a/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_5.png b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_5.png new file mode 100644 index 00000000..c48ae718 Binary files /dev/null and b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_5.png differ diff --git a/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_6.png b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_6.png new file mode 100644 index 00000000..9d81c0ae Binary files /dev/null and b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_6.png differ diff --git a/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_7.png b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_7.png new file mode 100644 index 00000000..6ca5e917 Binary files /dev/null and b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_7.png differ diff --git a/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_8.png b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_8.png new file mode 100644 index 00000000..ce2759e5 Binary files /dev/null and b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_8.png differ diff --git a/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_9.png b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_9.png new file mode 100644 index 00000000..03428e3b Binary files /dev/null and b/algorithms/greedy/boats/images/solutions/boats_to_save_people_solution_9.png differ diff --git a/algorithms/greedy/boats/test_boats_to_save_people.py b/algorithms/greedy/boats/test_boats_to_save_people.py new file mode 100644 index 00000000..23a836c1 --- /dev/null +++ b/algorithms/greedy/boats/test_boats_to_save_people.py @@ -0,0 +1,27 @@ +import unittest +from typing import List +from parameterized import parameterized +from algorithms.greedy.boats import rescue_boats + +TEST_CASES = [ + ([2, 1, 1], 3, 2), + ([3, 1, 4, 2, 4], 4, 4), + ([1, 1, 1, 1, 2], 3, 3), + ([1, 2], 3, 1), + ([5, 5, 5, 5], 5, 4), + ([3, 2, 5, 5], 5, 3), + ([1, 1, 1], 10, 2), + ([5], 5, 1), + ([3, 3, 3, 3], 3, 4), +] + + +class RescueBoatsTestCase(unittest.TestCase): + @parameterized.expand(TEST_CASES) + def test_rescue_boats(self, people: List[int], limit: int, expected: int): + actual = rescue_boats(people, limit) + self.assertEqual(expected, actual) + + +if __name__ == "__main__": + unittest.main() diff --git a/algorithms/greedy/spread_stones/README.md b/algorithms/greedy/spread_stones/README.md new file mode 100644 index 00000000..ff400559 --- /dev/null +++ b/algorithms/greedy/spread_stones/README.md @@ -0,0 +1,13 @@ +# Minimum Moves to Spread Stones Over Grid + +Given a 2D grid of integers of size (3 × 3), where each value represents the number of stones in the given cell, return +the minimum number of moves required to place exactly one stone in each grid cell. + +## Constraints + +- Only one stone can be moved in one move. +- Stone from a cell can only be moved to another cell if they are adjacent (share a side). +- The sum of all stones in the grid must be equal to 9. +- `grid.length`, `grid[i].length` == 3 +- 0 <= `grid[i][j]` <= 9 + diff --git a/algorithms/greedy/spread_stones/__init__.py b/algorithms/greedy/spread_stones/__init__.py new file mode 100644 index 00000000..d675ec5c --- /dev/null +++ b/algorithms/greedy/spread_stones/__init__.py @@ -0,0 +1,44 @@ +from typing import List +from itertools import permutations + + +def minimum_moves(grid: List[List[int]]) -> int: + """ + This function finds the minimum number of moves required to place exactly one stone in each grid cell. + + Args: + grid(list): 2D grid of integers of size (3 × 3), where each value represents the number of stones in the given + cell + Returns: + int: minimum number of moves required to place exactly one stone in each grid cell + """ + # stores every stone beyond the first one in a cell, this becomes the source + surplus_list = [] + # for every cell with 0 stones, this contains the coordinates of that cell. this becomes the target + empty_list = [] + + minimum_number_of_moves = float("inf") + + # iterate through the grid and add the cell that has surplus stones multiple times to the surplus list, for example, + # if cell grid[1][1] has 3 stones, add it twice to the surplus list + for row in range(len(grid)): + for col in range(len(grid[row])): + if grid[row][col] == 0: + empty_list.append([row, col]) + elif grid[row][col] > 1: + count = grid[row][col] + for c in range(count - 1): + surplus_list.append([row, col]) + + # iterate through every possible permutation of the sources, trying every possible assignment to the targets + for perms in permutations(surplus_list): + # calculate the total number of moves for this permutation + total_moves = 0 + for i in range(len(perms)): + total_moves += abs(perms[i][0] - empty_list[i][0]) + abs( + perms[i][1] - empty_list[i][1] + ) + # return the minimum number of moves + minimum_number_of_moves = min(minimum_number_of_moves, total_moves) + + return minimum_number_of_moves diff --git a/algorithms/greedy/spread_stones/test_minimum_moves_to_spread_stones.py b/algorithms/greedy/spread_stones/test_minimum_moves_to_spread_stones.py new file mode 100644 index 00000000..01d2c2e3 --- /dev/null +++ b/algorithms/greedy/spread_stones/test_minimum_moves_to_spread_stones.py @@ -0,0 +1,24 @@ +import unittest +from typing import List +from parameterized import parameterized +from algorithms.greedy.spread_stones import minimum_moves + + +TEST_CASES = [ + ([[0, 0, 1], [1, 0, 4], [1, 1, 1]], 6), + ([[0, 2, 1], [1, 1, 1], [1, 1, 1]], 1), + ([[0, 0, 0], [9, 0, 0], [0, 0, 0]], 15), + ([[6, 0, 0], [1, 0, 0], [1, 0, 1]], 11), + ([[0, 0, 0], [7, 0, 1], [0, 0, 1]], 10), +] + + +class MinimumMovesToSpreadStonesTestCase(unittest.TestCase): + @parameterized.expand(TEST_CASES) + def test_something(self, grid: List[List[int]], expected: int): + actual = minimum_moves(grid) + self.assertEqual(expected, actual) + + +if __name__ == "__main__": + unittest.main()