diff --git a/DIRECTORY.md b/DIRECTORY.md index 9d7cf5ed..8f87f61d 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -270,6 +270,9 @@ * [Test Task Scheduler](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/task_scheduler/test_task_scheduler.py) * Josephus Circle * [Test Josephus Circle](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/josephus_circle/test_josephus_circle.py) + * K Way Merge + * Kth Smallest Element In Matrix + * [Test Kth Smallest Element In Matrix](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/k_way_merge/kth_smallest_element_in_matrix/test_kth_smallest_element_in_matrix.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) diff --git a/algorithms/k_way_merge/__init__.py b/algorithms/k_way_merge/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/algorithms/k_way_merge/kth_smallest_element_in_matrix/README.md b/algorithms/k_way_merge/kth_smallest_element_in_matrix/README.md new file mode 100644 index 00000000..0a5e2d31 --- /dev/null +++ b/algorithms/k_way_merge/kth_smallest_element_in_matrix/README.md @@ -0,0 +1,92 @@ +# Kth Smallest Element in a Sorted Matrix + +Given an n x n matrix where each of the rows and columns is sorted in ascending order, return the kth smallest element +in the matrix. + +Note that it is the kth smallest element in the sorted order, not the kth distinct element. + +You must find a solution with a memory complexity better than O(n^2). + +## Examples + +![Example 1](./images/examples/kth_smallest_element_in_matrix_example_1.png) +![Example 2](./images/examples/kth_smallest_element_in_matrix_example_2.png) +![Example 3](./images/examples/kth_smallest_element_in_matrix_example_3.png) + +Example 4: + +```text +Input: matrix = [[1,5,9],[10,11,13],[12,13,15]], k = 8 +Output: 13 +Explanation: The elements in the matrix are [1,5,9,10,11,12,13,13,15], and the 8th smallest number is 13 +``` + +Example 5: +```text +Input: matrix = [[-5]], k = 1 +Output: -5 +``` + +## Constraints + +- n == `matrix.length` == `matrix[i].length` +- 1 <= n <= 300 +- -10^9 <= `matrix[i][j]` <= 10^9 +- All the rows and columns of matrix are guaranteed to be sorted in non-decreasing order. +- 1 <= k <= n^2 + +## Topics + +- Binary Search +- Sorting +- Heap (Priority Queue) +- Matrix + +## Solution + +A key observation when tackling this problem is that the matrix is sorted along rows and columns. This means that whether +we look at the matrix as a collection of rows or as a collection of columns, we see a collection of sorted lists. + +As we know, the k way merge pattern merges k-sorted arrays into a single sorted array using a heap. Therefore, to find +the `kth` smallest element in the matrix, we will use the same method where we will deal with the rows of the matrix as +k sorted arrays. So, this approach uses a min-heap and inserts the first element of each matrix row into the min-heap +(along with their respective row and column indexes for tracking). It then removes the top element of the heap(smallest +element) and checks whether the element has any next element in its row. If it has, that element is added to the heap. +This is repeated until k elements have been removed from the heap. The `kth` element removed is the `kth` smallest +element of the entire matrix. + +Here’s how we implement our algorithm using a min-heap to find the `kth` smallest element in a sorted matrix: + +1. We push the first element of each row of the matrix in the min-heap, storing each element along with its row and + column index. +2. Remove the top (root) of the min-heap. +3. If the popped element has the next element in its row, push the next element in the heap. +4. Repeat steps 2 and 3 as long as there are elements in the min-heap, and stop as soon as we’ve popped k elements from + it. +5. The last popped element in this process is the `kth` smallest element in the matrix. + +![Solution 1](./images/solutions/kth_smallest_element_in_matrix_solution_1.png) +![Solution 2](./images/solutions/kth_smallest_element_in_matrix_solution_2.png) +![Solution 3](./images/solutions/kth_smallest_element_in_matrix_solution_3.png) +![Solution 4](./images/solutions/kth_smallest_element_in_matrix_solution_4.png) +![Solution 5](./images/solutions/kth_smallest_element_in_matrix_solution_5.png) +![Solution 6](./images/solutions/kth_smallest_element_in_matrix_solution_6.png) +![Solution 7](./images/solutions/kth_smallest_element_in_matrix_solution_7.png) +![Solution 8](./images/solutions/kth_smallest_element_in_matrix_solution_8.png) + +### Time Complexity + +The time complexity of the first step is: + +- `O(min(n,k))` for iterating over whichever is the minimum of both, where n is the size of the matrix and k is the smallest + element we need to find. +- The push operation takes `O(log(m))` time, where m is the number of elements currently in the heap. However, since we’re + adding elements only `min(n,k)` elements, therefore, the time complexity of the first loop is `O(min(n,k)×log(min(n,k)))` +- In the while-loop, we pop and push `m` elements in the heap until we find the `kth` smallest element. In the worst case, + the heap could have up to `min(n,k)` elements. Therefore, the time complexity of this step is `O(klog(min(n,k)))` + +Overall, the total time complexity of this solution is `O((min(n,k)+k)×log(min(n,k)))` + +### Space Complexity + +The space complexity is O(n), where n is the total number of elements in the min-heap. diff --git a/algorithms/k_way_merge/kth_smallest_element_in_matrix/__init__.py b/algorithms/k_way_merge/kth_smallest_element_in_matrix/__init__.py new file mode 100644 index 00000000..22788fb1 --- /dev/null +++ b/algorithms/k_way_merge/kth_smallest_element_in_matrix/__init__.py @@ -0,0 +1,72 @@ +from typing import List, Tuple +import heapq + + +def kth_smallest_in_matrix_with_heap_1(matrix: List[List[int]], k: int) -> int: + """ + Finds the kth smallest element in a matrix that has its rows and columns sorted in ascending order. + Args: + matrix (List[List[int]]): The matrix to find the kth smallest element in. + k (int): The kth smallest element. + Returns: + int: The kth smallest element. + """ + if not matrix: + return -1 + + min_heap: List[int] = [] + + for lst in matrix: + for element in lst: + heapq.heappush(min_heap, element) + + counter = k - 1 + + while counter > 0: + heapq.heappop(min_heap) + counter -= 1 + + return min_heap[0] + + +def kth_smallest_in_matrix_with_heap_2(matrix: List[List[int]], k: int) -> int: + """ + Finds the kth smallest element in a matrix that has its rows and columns sorted in ascending order. + Args: + matrix (List[List[int]]): The matrix to find the kth smallest element in. + k (int): The kth smallest element. + Returns: + int: The kth smallest element. + """ + # storing the number of rows in the matrix to use it in later + row_count = len(matrix) + # declaring a min-heap to keep track of smallest elements + min_numbers: List[Tuple[int, int, int]] = [] + + for index in range(min(row_count, k)): + # pushing the first element of each row in the min-heap + # The heappush() method pushes an element into an existing heap + # in such a way that the heap property is maintained. + first_element = matrix[index][0] + element_index = 0 + heapq.heappush(min_numbers, (first_element, index, element_index)) + + numbers_checked, smallest_element = 0, 0 + # iterating over the elements pushed in our min-heap + while min_numbers: + # get the smallest number from top of heap and its corresponding row and column + smallest_element, row_index, col_index = heapq.heappop(min_numbers) + numbers_checked += 1 + # when numbers_checked equals k, we'll return smallest_element + if numbers_checked == k: + break + # if the current popped element has a next element in its row, + # add the next element of that row to the min-heap + if col_index + 1 < len(matrix[row_index]): + heapq.heappush( + min_numbers, + (matrix[row_index][col_index + 1], row_index, col_index + 1), + ) + + # return the Kth smallest element found in the matrix + return smallest_element diff --git a/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/examples/kth_smallest_element_in_matrix_example_1.png b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/examples/kth_smallest_element_in_matrix_example_1.png new file mode 100644 index 00000000..245139fd Binary files /dev/null and b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/examples/kth_smallest_element_in_matrix_example_1.png differ diff --git a/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/examples/kth_smallest_element_in_matrix_example_2.png b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/examples/kth_smallest_element_in_matrix_example_2.png new file mode 100644 index 00000000..c53480b2 Binary files /dev/null and b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/examples/kth_smallest_element_in_matrix_example_2.png differ diff --git a/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/examples/kth_smallest_element_in_matrix_example_3.png b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/examples/kth_smallest_element_in_matrix_example_3.png new file mode 100644 index 00000000..4507e99c Binary files /dev/null and b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/examples/kth_smallest_element_in_matrix_example_3.png differ diff --git a/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_1.png b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_1.png new file mode 100644 index 00000000..6f628032 Binary files /dev/null and b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_1.png differ diff --git a/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_2.png b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_2.png new file mode 100644 index 00000000..7e0c93ed Binary files /dev/null and b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_2.png differ diff --git a/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_3.png b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_3.png new file mode 100644 index 00000000..a5a89490 Binary files /dev/null and b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_3.png differ diff --git a/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_4.png b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_4.png new file mode 100644 index 00000000..1428ddb1 Binary files /dev/null and b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_4.png differ diff --git a/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_5.png b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_5.png new file mode 100644 index 00000000..92ad803e Binary files /dev/null and b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_5.png differ diff --git a/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_6.png b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_6.png new file mode 100644 index 00000000..a6b0e92b Binary files /dev/null and b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_6.png differ diff --git a/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_7.png b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_7.png new file mode 100644 index 00000000..031c12b5 Binary files /dev/null and b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_7.png differ diff --git a/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_8.png b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_8.png new file mode 100644 index 00000000..b7746e0d Binary files /dev/null and b/algorithms/k_way_merge/kth_smallest_element_in_matrix/images/solutions/kth_smallest_element_in_matrix_solution_8.png differ diff --git a/algorithms/k_way_merge/kth_smallest_element_in_matrix/test_kth_smallest_element_in_matrix.py b/algorithms/k_way_merge/kth_smallest_element_in_matrix/test_kth_smallest_element_in_matrix.py new file mode 100644 index 00000000..7982538c --- /dev/null +++ b/algorithms/k_way_merge/kth_smallest_element_in_matrix/test_kth_smallest_element_in_matrix.py @@ -0,0 +1,48 @@ +import unittest +from typing import List + +from parameterized import parameterized +from algorithms.k_way_merge.kth_smallest_element_in_matrix import ( + kth_smallest_in_matrix_with_heap_1, + kth_smallest_in_matrix_with_heap_2, +) + +KTH_SMALLEST_IN_SORTED_MATRIX_TEST_CASES = [ + ([[1, 5, 9], [10, 11, 13], [12, 13, 15]], 8, 13), + ([[-5]], 1, -5), + ([[2, 6, 8], [3, 7, 10], [5, 8, 11]], 3, 5), + ([[1, 2, 3], [4, 5, 6], [7, 8, 9]], 4, 4), + ([[1, 4], [2, 5]], 4, 5), + ([[1, 1, 1], [1, 1, 1], [1, 1, 1]], 5, 1), + ( + [ + [1, 3, 5, 7, 9], + [2, 4, 6, 8, 10], + [11, 13, 15, 17, 19], + [12, 14, 16, 18, 20], + [21, 22, 23, 24, 25], + ], + 11, + 11, + ), +] + + +class KthSmallestInMatrixTestCase(unittest.TestCase): + @parameterized.expand(KTH_SMALLEST_IN_SORTED_MATRIX_TEST_CASES) + def test_kth_smallest_in_matrix_1( + self, matrx: List[List[int]], k: int, expected: int + ): + actual = kth_smallest_in_matrix_with_heap_1(matrx, k) + self.assertEqual(expected, actual) + + @parameterized.expand(KTH_SMALLEST_IN_SORTED_MATRIX_TEST_CASES) + def test_kth_smallest_in_matrix_2( + self, matrx: List[List[int]], k: int, expected: int + ): + actual = kth_smallest_in_matrix_with_heap_2(matrx, k) + self.assertEqual(expected, actual) + + +if __name__ == "__main__": + unittest.main() diff --git a/datastructures/trees/binary/search_tree/bst_utils.py b/datastructures/trees/binary/search_tree/bst_utils.py index 022fbaab..4c01ea2b 100644 --- a/datastructures/trees/binary/search_tree/bst_utils.py +++ b/datastructures/trees/binary/search_tree/bst_utils.py @@ -32,7 +32,7 @@ def dfs(node: Optional[BinaryTreeNode], min_value: T, max_value: T) -> bool: def kth_smallest_element(root: BinaryTreeNode, k: int) -> Optional[BinaryTreeNode]: """ - Finds the Kth smallest element in a binary search tree. + Finds the Kth smallest element in a binary search tree. This function recursively performs the inorder traversal(left subtree, root, right subtree) on the binary search tree. We will use the inorder traversal to get elements in sorted order.