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
3 changes: 3 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Empty file.
92 changes: 92 additions & 0 deletions algorithms/k_way_merge/kth_smallest_element_in_matrix/README.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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
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.
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 1 addition & 1 deletion datastructures/trees/binary/search_tree/bst_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading