diff --git a/DIRECTORY.md b/DIRECTORY.md index 97d5f667..36d6b342 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -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 diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/README.md b/puzzles/arrays/lucky_numbers_in_a_matrix/README.md new file mode 100644 index 00000000..c7f8ff28 --- /dev/null +++ b/puzzles/arrays/lucky_numbers_in_a_matrix/README.md @@ -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 `yx`. 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). diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/__init__.py b/puzzles/arrays/lucky_numbers_in_a_matrix/__init__.py new file mode 100644 index 00000000..88f4a7c8 --- /dev/null +++ b/puzzles/arrays/lucky_numbers_in_a_matrix/__init__.py @@ -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 diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/examples/lucky_numbers_in_a_matrix_example_1.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/examples/lucky_numbers_in_a_matrix_example_1.png new file mode 100644 index 00000000..6d60f286 Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/examples/lucky_numbers_in_a_matrix_example_1.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/examples/lucky_numbers_in_a_matrix_example_2.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/examples/lucky_numbers_in_a_matrix_example_2.png new file mode 100644 index 00000000..8fccadc1 Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/examples/lucky_numbers_in_a_matrix_example_2.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/examples/lucky_numbers_in_a_matrix_example_3.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/examples/lucky_numbers_in_a_matrix_example_3.png new file mode 100644 index 00000000..81d11b93 Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/examples/lucky_numbers_in_a_matrix_example_3.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_1.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_1.png new file mode 100644 index 00000000..0c67571b Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_1.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_10.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_10.png new file mode 100644 index 00000000..8183dcf8 Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_10.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_11.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_11.png new file mode 100644 index 00000000..089bc6fc Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_11.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_12.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_12.png new file mode 100644 index 00000000..a14c10ba Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_12.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_13.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_13.png new file mode 100644 index 00000000..c12af676 Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_13.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_14.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_14.png new file mode 100644 index 00000000..f9d80738 Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_14.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_15.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_15.png new file mode 100644 index 00000000..d55c5757 Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_15.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_16.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_16.png new file mode 100644 index 00000000..e0669241 Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_16.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_2.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_2.png new file mode 100644 index 00000000..609b71dd Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_2.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_3.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_3.png new file mode 100644 index 00000000..cf9c2a54 Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_3.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_4.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_4.png new file mode 100644 index 00000000..d66b7643 Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_4.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_5.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_5.png new file mode 100644 index 00000000..dfb3432f Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_5.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_6.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_6.png new file mode 100644 index 00000000..f11d01d9 Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_6.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_7.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_7.png new file mode 100644 index 00000000..46b33eed Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_7.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_8.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_8.png new file mode 100644 index 00000000..79a150d7 Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_8.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_9.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_9.png new file mode 100644 index 00000000..6730343a Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_in_a_matrix_solution_9.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_proof_of_contradiction.png b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_proof_of_contradiction.png new file mode 100644 index 00000000..dfc9c8bd Binary files /dev/null and b/puzzles/arrays/lucky_numbers_in_a_matrix/images/solutions/lucky_numbers_proof_of_contradiction.png differ diff --git a/puzzles/arrays/lucky_numbers_in_a_matrix/test_lucky_numbers_in_a_matrix.py b/puzzles/arrays/lucky_numbers_in_a_matrix/test_lucky_numbers_in_a_matrix.py new file mode 100644 index 00000000..aded4d70 --- /dev/null +++ b/puzzles/arrays/lucky_numbers_in_a_matrix/test_lucky_numbers_in_a_matrix.py @@ -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()