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
2 changes: 2 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,8 @@
* [Test Longest Increasing Subsequence](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/arrays/longest_increasing_subsequence/test_longest_increasing_subsequence.py)
* Longest Subarray Of Ones
* [Test Longest Subarray](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/arrays/longest_subarray_of_ones/test_longest_subarray.py)
* Lucky Numbers In A Matrix
* [Test Lucky Numbers In A Matrix](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/arrays/lucky_numbers_in_a_matrix/test_lucky_numbers_in_a_matrix.py)
* Max Consecutive Ones
* [Test Max Consecutive Ones](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/arrays/max_consecutive_ones/test_max_consecutive_ones.py)
* Max Number Of Ksum Pairs
Expand Down
146 changes: 146 additions & 0 deletions puzzles/arrays/lucky_numbers_in_a_matrix/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Lucky Numbers in a Matrix

Given an m × n matrix of distinct numbers, return the lucky number in the matrix.

> A lucky number is an element of the matrix such that it is the smallest element in its row and largest in its column.

Constraints

- m = `matrix.length`
- n = `matrix[i].length`
- 1 <= m, n <= 50
- 1 <= `matrix[i][j]` <= 10^5
- All elements in the matrix are distinct.

## Examples

![Example 1](./images/examples/lucky_numbers_in_a_matrix_example_1.png)
![Example 2](./images/examples/lucky_numbers_in_a_matrix_example_2.png)
![Example 3](./images/examples/lucky_numbers_in_a_matrix_example_3.png)

## Solution: Greedy

The core idea behind the solution is to recognize that there can be, at most, one lucky number in the matrix. This is
proven by contradiction, as having two such numbers would violate the unique conditions for being a lucky number.

> **Proof by contradiction:**
>
> Suppose we have an integer x located at row r1 and column c1 in a matrix. The integer x is the smallest value in its
> row and the largest value in its column, making it a lucky number.
> Now, assume another integer y exists in row r2 and column c2. For the sake of argument, let’s assume y is also a
> lucky number, meaning it is the smallest value in its row and the largest in its column.
> We assess these assumptions using the following steps:
>
> 1. As `y` is a lucky number, the smallest value is in row r2 and the largest value is in column c2. Let’s denote the
> integer at position (r2,c2) as `a`
> - Then, `y` < `a` because `y` is the minimum in its row
> - Then, `x` > `a` because `x` is the maximum in it column.
>
> Therefore `y` < `x`
>
> 2. Next, let's consider the integer at position (r1, c2), which we'll call `b`
> - Then, `y` > `b` because `y` is the maximum in its column
> - Then, `x` < `b` because `x` is the minimum in its row
>
> Therefore `y` > `x`
>
> This leads to a contradiction, as we deduced `y<x` and `y>x`. This inconsistency implies that our initial assumption—
> that y is a lucky number—is incorrect. Therefore, only x can be the lucky number in this configuration.
>
> Visually, this looks like this:
>
> ![Proof by contradiction](./images/solutions/lucky_numbers_proof_of_contradiction.png)

This problem can be solved using a greedy algorithm that analyzes the matrix row by row and column by column.

We start by iterating over the rows to find the minimum values. Out of those minimum values, we choose the largest
minimum value and store it in r_largest_min. Similarly, we calculate the maximum values in columns and after finding
all the largest values, we choose the smallest of them and store them in c_smallest_max. Once we have found the values
in both rows and columns, we match them to see if they are the same. If they are, we return either of the values;
r_largest_min or c_smallest_max. Otherwise if now matching value is found, we return an empty matrix.

Following are the detailed steps of the algorithm that we have just discussed:

1. We define two variables, r_largest_min and c_smallest_max:

- r_largest_min is set to negative infinity (float('-inf')) to ensure any row’s minimum value can be updated.
- c_smallest_max is set to positive infinity (float('inf')) to ensure any column’s maximum value can be updated.

2. For each row in the matrix:

- We calculate the minimum value of the row (r_min).
- Then, we update r_largest_min to the maximum of r_largest_min and r_min.
- The steps above ensure we consider only minimum values in their rows, narrowing the candidate set for a lucky number.

3. For each column in the matrix:

- We calculate the maximum value of the column (c_max) by iterating over all rows.
- Next, we update c_max_min to the minimum of c_smallest_max and c_max.
- The above steps ensure we consider only maximum values in their columns, further narrowing the candidate set for a
lucky number.

4. Finally, we compare whether r_largest_min equals c_smallest_max. If TRUE, we return the value stored in [r_largest_min].
Otherwise, we return an empty array []. The comparison ensures that the identified value satisfies both conditions of
being the minimum in its row and the maximum in its column, making it a valid lucky number.

![Solution 1](./images/solutions/lucky_numbers_in_a_matrix_solution_1.png)
![Solution 2](./images/solutions/lucky_numbers_in_a_matrix_solution_2.png)
![Solution 3](./images/solutions/lucky_numbers_in_a_matrix_solution_3.png)
![Solution 4](./images/solutions/lucky_numbers_in_a_matrix_solution_4.png)
![Solution 5](./images/solutions/lucky_numbers_in_a_matrix_solution_5.png)
![Solution 6](./images/solutions/lucky_numbers_in_a_matrix_solution_6.png)
![Solution 7](./images/solutions/lucky_numbers_in_a_matrix_solution_7.png)
![Solution 8](./images/solutions/lucky_numbers_in_a_matrix_solution_8.png)
![Solution 9](./images/solutions/lucky_numbers_in_a_matrix_solution_9.png)
![Solution 10](./images/solutions/lucky_numbers_in_a_matrix_solution_10.png)
![Solution 11](./images/solutions/lucky_numbers_in_a_matrix_solution_11.png)
![Solution 12](./images/solutions/lucky_numbers_in_a_matrix_solution_12.png)
![Solution 13](./images/solutions/lucky_numbers_in_a_matrix_solution_13.png)
![Solution 14](./images/solutions/lucky_numbers_in_a_matrix_solution_14.png)
![Solution 15](./images/solutions/lucky_numbers_in_a_matrix_solution_15.png)
![Solution 16](./images/solutions/lucky_numbers_in_a_matrix_solution_16.png)

### Time Complexity

The time complexity of the solution is O(m×n), where m is the number of columns in the matrix and n is the number of
rows in the matrix.

### Space Complexity

The solution’s space complexity is O(1) as no extra space is required apart from the few variables.

## Solution: Simulation

We are given a matrix of size MXN with distinct integers. We need to return the list of lucky numbers in the matrix.
An integer in the matrix is lucky if it is the maximum integer in its column and it is the minimum value in its row.

In this approach, we will simulate the process by iterating over each integer in the matrix, checking if it is the
maximum in its row and the minimum in its column. If it meets both criteria, we will add it to the list of lucky numbers,
luckyNumbers.

The naive approach to check the criteria for each integer involves iterating over each integer in the current row and
column to verify the minimum and maximum criteria, requiring M+N operations per integer. A more efficient method is to
precompute the minimum of each row and the maximum of each column before processing the matrix. This allows us to check
the criteria for each integer in constant time. We iterate over each row to store the minimum in rowMin and each column
to store the maximum in colMax.

### Algorithm

1. Iterate over each row and store the minimum of the ith row at the ith position in the list rowMin.
2. Iterate over each column and store the maximum of the ith column at the ith position in the list colMax.
3. Iterate over each integer in the matrix and for each integer at (i, j), check if the integer is equal to rowMin[i]
and colMax[j]. If yes, add it to the list luckyNumbers.
4. Return luckyNumbers.

### Complexity Analysis

Here, N is the number of rows in the matrix and M is the number of columns in the matrix.

#### Time complexity: O(N*M).

To store the maximum of each row, we require N*M operations and the same for strong the maximum of each column. In the
end, to find the lucky numbers we again iterate over each integer. Hence, the total time complexity is equal to O(N*M).

#### Space complexity: O(N+M).

We require two lists, rowMin and colMax of size N and M respectively. Hence the total space complexity is equal to O(N+M).
99 changes: 99 additions & 0 deletions puzzles/arrays/lucky_numbers_in_a_matrix/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from typing import List


def lucky_numbers(matrix: List[List[int]]) -> List[int]:
"""
This function takes a matrix as input and returns a list containing the lucky number(s) if they exist.

A lucky number is a number that is the maximum of the minimum values from each row and the minimum of the maximum
values from each column.

If a lucky number exists, the function returns a list containing that number. Otherwise, it returns an empty list.

Time Complexity: O(m * n) where m is the number of columns and n is the number of rows in the matrix.
Space Complexity: O(1) as we are using a constant amount of extra space.

Args:
matrix (List[List[int]]): The input matrix.

Returns:
List[int]: A list containing the lucky number(s) if they exist, otherwise an empty list.
"""
row_length = len(matrix)
col_length = len(matrix[0])

# initialize a variable to keep track of the maximum of the minimum values from each row
r_largest_min = float("-inf")

# We start by iterating over the rows to find the minimum values. Out of those minimum values, we choose the
# largest minimum value and store it in r_largest_min
for i in range(row_length):
# find the minimum value in current row
row_min = min(matrix[i])
# update r_largest_min to be the maximum of current r_largest_min and the current row minimum
r_largest_min = max(r_largest_min, row_min)

# initialize a variable to keep track of the minimum of the maximum values from each column
c_smallest_max = float("inf")
# Similarly, we calculate the maximum values in columns and after finding all the largest values, we choose the
# smallest of them and store them in c_smallest_max
for c in range(col_length):
# find the maximum value in the current row
col_max = max(matrix[r][c] for r in range(row_length))
# update c_smallest_max to be the minium of the current c_smallest_max and the current column maximum
c_smallest_max = min(c_smallest_max, col_max)

# If they are, we return either of the values; r_largest_min or c_smallest_max. Otherwise if now matching value is
# found, we return an empty matrix.

# check if the maximum of row minima is equal to the minimum of column maxima
if r_largest_min == c_smallest_max:
# if they are equal, return a list containing the luky number
return [r_largest_min]
# Otherwise, return an empty list indicating no luky number exists
return []


def lucky_numbers_simulation(matrix: List[List[int]]) -> List[int]:
"""
This function takes a matrix as input and returns a list containing all the lucky number(s) if they exist.

A lucky number is a number that is the maximum of the minimum values from each row and the minimum of the maximum
values from each column.

The function first calculates the minimum values from each row and the maximum values from each column, then compares
these two lists to find the lucky number(s).

Time Complexity: O(m * n) where m is the number of columns and n is the number of rows in the matrix.
Space Complexity: O(m + n) as we are using two lists of size m and n respectively.

Args:
matrix (List[List[int]]): The input matrix.

Returns:
List[int]: A list containing all the lucky number(s) if they exist, otherwise an empty list.
"""
row_length = len(matrix)
col_length = len(matrix[0])

row_min = []
for i in range(row_length):
r_min = float('inf')
for j in range(col_length):
r_min = min(r_min, matrix[i][j])
row_min.append(r_min)

col_max = []
for i in range(col_length):
c_max = float('-inf')
for j in range(row_length):
c_max = max(c_max, matrix[j][i])
col_max.append(c_max)

result = []
for i in range(row_length):
for j in range(col_length):
if matrix[i][j] == row_min[i] and matrix[i][j] == col_max[j]:
result.append(matrix[i][j])

return result
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.
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,73 @@
import unittest
from typing import List
from parameterized import parameterized
from puzzles.arrays.lucky_numbers_in_a_matrix import lucky_numbers, lucky_numbers_simulation


class LuckyNumbersInAMatrixTestCase(unittest.TestCase):

@parameterized.expand(
[
([[3, 7, 8], [9, 11, 13], [15, 16, 17]], [15]),
([[1, 2, 3], [4, 5, 6], [7, 8, 9]], [7]),
(
[
[10, 20, 30, 40],
[5, 25, 35, 50],
[60, 70, 80, 90],
[100, 110, 120, 130],
],
[100],
),
(
[[12, 18, 23, 50], [5, 16, 25, 45], [4, 15, 26, 48], [3, 14, 27, 60]],
[12],
),
([[5]], [5]),
([[30, 20, 10], [40, 50, 60], [70, 80, 90]], [70]),
([[5, 1, 9], [10, 8, 2], [7, 3, 6]], []),
([[22, 11], [88, 77], [55, 44]], [77]),
([[1,10,4,2],[9,3,8,7],[15,16,17,12]], [12]),
([[7,8],[1,2]], [7]),
]
)
def test_lucky_numbers_in_matrix(
self, matrix: List[List[int]], expected: List[int]
):
actual = lucky_numbers(matrix)
self.assertEqual(expected, actual)

@parameterized.expand(
[
([[3, 7, 8], [9, 11, 13], [15, 16, 17]], [15]),
([[1, 2, 3], [4, 5, 6], [7, 8, 9]], [7]),
(
[
[10, 20, 30, 40],
[5, 25, 35, 50],
[60, 70, 80, 90],
[100, 110, 120, 130],
],
[100],
),
(
[[12, 18, 23, 50], [5, 16, 25, 45], [4, 15, 26, 48], [3, 14, 27, 60]],
[12],
),
([[5]], [5]),
([[30, 20, 10], [40, 50, 60], [70, 80, 90]], [70]),
([[5, 1, 9], [10, 8, 2], [7, 3, 6]], []),
([[22, 11], [88, 77], [55, 44]], [77]),
([[1,10,4,2],[9,3,8,7],[15,16,17,12]], [12]),
([[7,8],[1,2]], [7]),
]
)
def test_lucky_numbers_in_matrix_simulation(
self, matrix: List[List[int]], expected: List[int]
):
actual = lucky_numbers_simulation(matrix)
self.assertEqual(expected, actual)


if __name__ == "__main__":
unittest.main()
Loading