diff --git a/DIRECTORY.md b/DIRECTORY.md index 5138906f..d4d8437b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -260,6 +260,8 @@ * Josephus Circle * [Test Josephus Circle](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/josephus_circle/test_josephus_circle.py) * Matrix + * Best Meeting Point + * [Test Best Meeting Point](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/matrix/best_meeting_point/test_best_meeting_point.py) * Isvalidsudoku * [Test Is Valid Sudoku](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/matrix/isvalidsudoku/test_is_valid_sudoku.py) * Memoization diff --git a/algorithms/dynamic_programming/min_path_sum/__init__.py b/algorithms/dynamic_programming/min_path_sum/__init__.py index 1d0ccb34..0cc94e46 100644 --- a/algorithms/dynamic_programming/min_path_sum/__init__.py +++ b/algorithms/dynamic_programming/min_path_sum/__init__.py @@ -71,13 +71,13 @@ def min_path_sum_grid(grid: List[List[int]]) -> int: for i in range(m): for j in range(n): if i == 0 and j > 0: - # First row but not [0][0]: we can only come from the left + # First row but not [0][0]: we can only come from the left grid[i][j] += grid[i][j - 1] elif j == 0 and i > 0: # First column but not [0][0]: we can only come from above grid[i][j] += grid[i - 1][j] elif i > 0 and j > 0: - # For all other cells, choose the minimum of: + # For all other cells, choose the minimum of: # - the path sum from above (i-1, j) # - the path sum from the left (i, j-1) grid[i][j] += min(grid[i - 1][j], grid[i][j - 1]) diff --git a/algorithms/dynamic_programming/min_path_sum/test_min_path_sum.py b/algorithms/dynamic_programming/min_path_sum/test_min_path_sum.py index 5d667cf6..cd3d274e 100644 --- a/algorithms/dynamic_programming/min_path_sum/test_min_path_sum.py +++ b/algorithms/dynamic_programming/min_path_sum/test_min_path_sum.py @@ -2,7 +2,12 @@ import copy from typing import List from parameterized import parameterized -from algorithms.dynamic_programming.min_path_sum import min_path_sum_in_triangle, min_path_sum_in_triangle_2, min_path_sum_grid, min_path_sum_grid_2 +from algorithms.dynamic_programming.min_path_sum import ( + min_path_sum_in_triangle, + min_path_sum_in_triangle_2, + min_path_sum_grid, + min_path_sum_grid_2, +) MIN_PATH_SUM_TRIANGLE_TEST_CASES = [ ([[5]], 5), @@ -13,8 +18,8 @@ ] MIN_PATH_SUM_GRID_TEST_CASES = [ - ([[1,3,1],[1,5,1],[4,2,1]], 7), - ([[1,2,3],[4,5,6]], 12), + ([[1, 3, 1], [1, 5, 1], [4, 2, 1]], 7), + ([[1, 2, 3], [4, 5, 6]], 12), ([[1, 2, 5], [3, 2, 1]], 6), ([[5, 9, 1, 3], [4, 2, 1, 7], [3, 1, 1, 2]], 15), ([[5]], 5), diff --git a/algorithms/intervals/min_intervals_for_queries/__init__.py b/algorithms/intervals/min_intervals_for_queries/__init__.py index d0afe0ac..6bbb3e4d 100644 --- a/algorithms/intervals/min_intervals_for_queries/__init__.py +++ b/algorithms/intervals/min_intervals_for_queries/__init__.py @@ -3,7 +3,7 @@ def min_interval(intervals: List[List[int]], queries: List[int]) -> List[int]: - query_len=len(queries) + query_len = len(queries) query_indexes = list(range(query_len)) query_indexes.sort(key=lambda q: queries[q]) diff --git a/algorithms/matrix/best_meeting_point/README.md b/algorithms/matrix/best_meeting_point/README.md new file mode 100644 index 00000000..02c4d8fc --- /dev/null +++ b/algorithms/matrix/best_meeting_point/README.md @@ -0,0 +1,79 @@ +# Best Meeting Point + +You are given a 2D grid of size m×n, where each cell contains either a 0 or a 1. A 1 represents the home of a friend, +and a 0 represents an empty space. + +Your task is to return the minimum total travel distance to a meeting point. The total travel distance is the sum of the +Manhattan distances between each friend’s home and the meeting point. + +The **Manhattan Distance** between two points `(x1, y1)` and `(x2, y2)` is calculated as: +`|x2 - x1| + |y2 - y1|`. + +## Constraints + +- m == grid.length +- n == grid[i].length +- 1 ≤ m, n ≤ 50 +- `grid[i][j]` is either 0 or 1. +- There will be at least two friends in the grid. + +## Examples + +![Example 1](./images/examples/best_meeting_point_example_1.png) +![Example 2](./images/examples/best_meeting_point_example_2.png) +![Example 3](./images/examples/best_meeting_point_example_3.png) + +## Solution + +The main idea of this algorithm is that the total Manhattan distance is minimized when all friends meet at the median +position, calculated separately for rows and columns. As Manhattan distance can be split into vertical and horizontal +components, we collect all the row indices and column indices of the friends and compute the distance to their respective +medians. As we loop through the grid row-wise and column-wise, the row and column indices are gathered in sorted order +naturally, so no additional sorting is needed. Finally, a two-pointer approach is used to efficiently compute the total +distance by pairing positions from both ends toward the center. + +Using the intuition above, we implement the algorithm as follows: + +1. Create two vectors, `rows` and `cols`, to store all cells’ row and column indexes where `grid[i][j] == 1`. +2. Iterate through the grid row by row. For each cell that contains a 1, push the row index `i` into the `rows` vector. +3. Iterate through the grid column by column. For each cell that contains a `1`, push the column index j into the `cols` vector. +4. Use the helper function getMinDistance(rows) to calculate the total vertical distance to the optimal row (median). +5. Use the helper function getMinDistance(cols) to calculate the total horizontal distance to the optimal column (median). +6. Return the sum of the two distances as the minimum total travel distance. + +The getMinDistance helper function receives a list of positions, points, and returns the total minimum travel distance +to the median. The points list contains either row or column indices of friends. As the Manhattan distance is minimized +at the median, it uses a two-pointer technique as follows: + +- Initialize a variable, distance, with 0 to compute the total distance. +- Initialize two pointers, i and j, one at the start and the other at the end. +- Each step adds the difference points[j] - points[i] to the total distance. +- This process continues until the pointers meet. +- Returns the total computed distance. + +![Solution 1](./images/solutions/best_meeting_point_solution_1.png) +![Solution 2](./images/solutions/best_meeting_point_solution_2.png) +![Solution 3](./images/solutions/best_meeting_point_solution_3.png) +![Solution 4](./images/solutions/best_meeting_point_solution_4.png) +![Solution 5](./images/solutions/best_meeting_point_solution_5.png) +![Solution 6](./images/solutions/best_meeting_point_solution_6.png) +![Solution 7](./images/solutions/best_meeting_point_solution_7.png) +![Solution 8](./images/solutions/best_meeting_point_solution_8.png) +![Solution 9](./images/solutions/best_meeting_point_solution_9.png) +![Solution 10](./images/solutions/best_meeting_point_solution_10.png) +![Solution 11](./images/solutions/best_meeting_point_solution_11.png) +![Solution 12](./images/solutions/best_meeting_point_solution_12.png) +![Solution 13](./images/solutions/best_meeting_point_solution_13.png) + +### Time Complexity + +The time complexity of the above algorithm is `O(m×n+k)`, where m×n are the dimensions of the grid and k is the number +of friends (number of 1s in the grid). This is because: + +- O(m×n) to traverse the entire grid and collect row and column indices. +- O(k) to compute distances in getMinDistance, where k is the number of friends. + +### Space Complexity + +The algorithm’s space complexity is `O(k)` because we store up to k row indices and k column indices in two separate +vectors. diff --git a/algorithms/matrix/best_meeting_point/__init__.py b/algorithms/matrix/best_meeting_point/__init__.py new file mode 100644 index 00000000..c069017f --- /dev/null +++ b/algorithms/matrix/best_meeting_point/__init__.py @@ -0,0 +1,84 @@ +from typing import List + + +def min_total_distance(grid: List[List[int]]) -> int: + rows, cols = [], [] + + # Helper function to calculate total distance to the median + def get_min_distance(points: List[int]) -> int: + distance = 0 + i, j = 0, len(points) - 1 + + # Use two pointers to accumulate distance from both ends toward the center + while i < j: + distance += points[j] - points[i] + i += 1 + j -= 1 + + return distance + + # Collect all row indices where grid[i][j] == 1 + for i in range(len(grid)): + for j in range(len(grid[0])): + if grid[i][j] == 1: + rows.append(i) + + # Collect all column indices where grid[i][j] == 1 + for j in range(len(grid[0])): + for i in range(len(grid)): + if grid[i][j] == 1: + cols.append(j) + + # Compute total vertical and horizontal distances to medians + return get_min_distance(rows) + get_min_distance(cols) + + +def min_total_distance_2(grid: List[List[int]]) -> int: + """ + Find the minimum total distance for all people to meet at one point. + The optimal meeting point is the median of all x-coordinates and y-coordinates. + + Args: + grid: 2D grid where 1 represents a person's location, 0 represents empty space + + Returns: + Minimum total Manhattan distance for all people to meet + """ + + def calculate_distance_sum(positions: List[int], meeting_point: int) -> int: + """ + Calculate sum of distances from all positions to the meeting point. + + Args: + positions: List of coordinate values (either row or column indices) + meeting_point: The target coordinate to measure distance to + + Returns: + Sum of absolute distances + """ + return sum(abs(position - meeting_point) for position in positions) + + # Collect all row and column indices where people are located + row_indices = [] + column_indices = [] + + for row_index, row in enumerate(grid): + for column_index, cell_value in enumerate(row): + if cell_value == 1: # Person found at this location + row_indices.append(row_index) + column_indices.append(column_index) + + # Sort column indices to find median (row indices already sorted due to iteration order) + column_indices.sort() + + # Find median positions (optimal meeting point) + # Using bit shift for integer division by 2 + median_row = row_indices[len(row_indices) >> 1] + median_column = column_indices[len(column_indices) >> 1] + + # Calculate total distance as sum of row distances and column distances + total_distance = calculate_distance_sum( + row_indices, median_row + ) + calculate_distance_sum(column_indices, median_column) + + return total_distance diff --git a/algorithms/matrix/best_meeting_point/images/examples/best_meeting_point_example_1.png b/algorithms/matrix/best_meeting_point/images/examples/best_meeting_point_example_1.png new file mode 100644 index 00000000..293b9a09 Binary files /dev/null and b/algorithms/matrix/best_meeting_point/images/examples/best_meeting_point_example_1.png differ diff --git a/algorithms/matrix/best_meeting_point/images/examples/best_meeting_point_example_2.png b/algorithms/matrix/best_meeting_point/images/examples/best_meeting_point_example_2.png new file mode 100644 index 00000000..e730c30b Binary files /dev/null and b/algorithms/matrix/best_meeting_point/images/examples/best_meeting_point_example_2.png differ diff --git a/algorithms/matrix/best_meeting_point/images/examples/best_meeting_point_example_3.png b/algorithms/matrix/best_meeting_point/images/examples/best_meeting_point_example_3.png new file mode 100644 index 00000000..7b5adcfb Binary files /dev/null and b/algorithms/matrix/best_meeting_point/images/examples/best_meeting_point_example_3.png differ diff --git a/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_1.png b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_1.png new file mode 100644 index 00000000..1b2e621d Binary files /dev/null and b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_1.png differ diff --git a/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_10.png b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_10.png new file mode 100644 index 00000000..e3d8e0e8 Binary files /dev/null and b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_10.png differ diff --git a/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_11.png b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_11.png new file mode 100644 index 00000000..5b20537f Binary files /dev/null and b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_11.png differ diff --git a/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_12.png b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_12.png new file mode 100644 index 00000000..2d745bd4 Binary files /dev/null and b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_12.png differ diff --git a/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_13.png b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_13.png new file mode 100644 index 00000000..6216ee3f Binary files /dev/null and b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_13.png differ diff --git a/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_2.png b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_2.png new file mode 100644 index 00000000..f2477233 Binary files /dev/null and b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_2.png differ diff --git a/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_3.png b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_3.png new file mode 100644 index 00000000..4d7ea181 Binary files /dev/null and b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_3.png differ diff --git a/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_4.png b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_4.png new file mode 100644 index 00000000..ce4445a3 Binary files /dev/null and b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_4.png differ diff --git a/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_5.png b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_5.png new file mode 100644 index 00000000..1c7daeb6 Binary files /dev/null and b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_5.png differ diff --git a/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_6.png b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_6.png new file mode 100644 index 00000000..88200fa9 Binary files /dev/null and b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_6.png differ diff --git a/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_7.png b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_7.png new file mode 100644 index 00000000..c2279ee4 Binary files /dev/null and b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_7.png differ diff --git a/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_8.png b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_8.png new file mode 100644 index 00000000..eed41e60 Binary files /dev/null and b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_8.png differ diff --git a/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_9.png b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_9.png new file mode 100644 index 00000000..77ec6172 Binary files /dev/null and b/algorithms/matrix/best_meeting_point/images/solutions/best_meeting_point_solution_9.png differ diff --git a/algorithms/matrix/best_meeting_point/test_best_meeting_point.py b/algorithms/matrix/best_meeting_point/test_best_meeting_point.py new file mode 100644 index 00000000..e23ebd63 --- /dev/null +++ b/algorithms/matrix/best_meeting_point/test_best_meeting_point.py @@ -0,0 +1,29 @@ +import unittest +from typing import List +from parameterized import parameterized +from algorithms.matrix.best_meeting_point import ( + min_total_distance, + min_total_distance_2, +) + +BEST_MEETING_POINT_TEST_CASES = [ + ([[1, 0, 0], [0, 0, 0], [0, 0, 1]], 4), + ([[1, 1]], 1), + ([[0, 0, 1], [1, 0, 0], [0, 0, 1]], 4), +] + + +class BestMeetingPointTestCase(unittest.TestCase): + @parameterized.expand(BEST_MEETING_POINT_TEST_CASES) + def test_min_total_distance(self, grid: List[List[int]], expected: int): + actual = min_total_distance(grid) + self.assertEqual(expected, actual) + + @parameterized.expand(BEST_MEETING_POINT_TEST_CASES) + def test_min_total_distance_2(self, grid: List[List[int]], expected: int): + actual = min_total_distance_2(grid) + self.assertEqual(expected, actual) + + +if __name__ == "__main__": + unittest.main()