Lucky Numbers in a Matrix
EasyPrompt
Given an m x n matrix of distinct numbers, return all lucky numbers in the matrix in any order.
A lucky number is an element of the matrix such that it is the minimum element in its row and maximum in its column.
Example 1:
Input: matrix = [[3,7,8],[9,11,13],[15,16,17]]
Output: [15]
Explanation: 15 is the only lucky number since it is the minimum in its row and the maximum in its column.Example 2:
Input: matrix = [[1,10,4,2],[9,3,8,7],[15,16,17,12]]
Output: [12]
Explanation: 12 is the only lucky number since it is the minimum in its row and the maximum in its column.Example 3:
Input: matrix = [[7,8],[1,2]]
Output: [7]
Explanation: 7 is the only lucky number since it is the minimum in its row and the maximum in its column.
Constraints:
m == mat.lengthn == mat[i].length1 <= n, m <= 501 <= matrix[i][j] <= 105.- All elements in the matrix are distinct.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach improves upon the brute-force method by reducing redundant checks. Instead of checking every element, we first identify the minimum element in each row. Only these row-minimums are candidates for being lucky numbers. Then, for each of these candidates, we check if it is the maximum in its respective column.
Algorithm
- Initialize an empty list
luckyNumbers. - Iterate through each row
ifrom 0 tom-1. - Find the minimum value
minValand its column indexminIndexin the current rowi. - Assume
minValis a lucky number (i.e., it's the maximum in its column). LetisMaxInCol = true. - Iterate through each row
kfrom 0 tom-1to check columnminIndex. -
If `matrix[k][minIndex] > minVal`, then `minVal` is not the maximum in its column. Set `isMaxInCol = false` and break the inner loop. - If
isMaxInColremains true after checking the entire column, addminValto theluckyNumberslist. - Return
luckyNumbers.
Walkthrough
The algorithm iterates through each row of the matrix one by one. For each row i, it finds the minimum value minVal and its column index minIndex. This is done with a single scan of the row. Once the row minimum minVal is found, it is a potential lucky number. The first condition (minimum in its row) is already satisfied. The next step is to verify the second condition: whether minVal is the maximum element in its column minIndex. To do this, we iterate through all elements in column minIndex and compare them with minVal. If no element in the column is greater than minVal, it satisfies the second condition and is added to the result list. This process is repeated for all rows.
import java.util.ArrayList;import java.util.List; class Solution { public List<Integer> luckyNumbers (int[][] matrix) { List<Integer> luckyNumbers = new ArrayList<>(); int m = matrix.length; int n = matrix[0].length; for (int i = 0; i < m; i++) { // Find the minimum element in the current row int minVal = matrix[i][0]; int minIndex = 0; for (int j = 1; j < n; j++) { if (matrix[i][j] < minVal) { minVal = matrix[i][j]; minIndex = j; } } // Check if this element is the maximum in its column boolean isMaxInCol = true; for (int k = 0; k < m; k++) { if (matrix[k][minIndex] > minVal) { isMaxInCol = false; break; } } if (isMaxInCol) { luckyNumbers.add(minVal); } } return luckyNumbers; }}Complexity
Time
O(m * (n + m)). For each of the `m` rows, we spend `O(n)` to find the minimum and `O(m)` to check the column.
Space
O(1) auxiliary space, not counting the space for the output list.
Trade-offs
Pros
More efficient than the pure brute-force approach.
Still maintains low space complexity.
Cons
Can still be slow if the number of rows (
m) is large, as the complexity isO(m*n + m^2).
This is the most efficient approach. It avoids nested checks by pre-calculating all row minimums and all column maximums in two separate passes. A number is a lucky number if and only if it is present in both the list of row minimums and the list of column maximums.
Algorithm
- Get matrix dimensions
mandn. - Create an array
rowMinsof sizem. - Iterate
ifrom 0 tom-1:- Find the minimum value in
matrix[i]and store it inrowMins[i].
- Find the minimum value in
- Create an array
colMaxsof sizen. - Iterate
jfrom 0 ton-1:- Find the maximum value in column
jof the matrix and store it incolMaxs[j].
- Find the maximum value in column
- Initialize an empty list
luckyNumbers. - Create a
HashSet<Integer>and add all elements fromrowMinsto it. - Iterate through each value
maxValincolMaxs:- If the set contains
maxVal, addmaxValtoluckyNumbers.
- If the set contains
- Return
luckyNumbers.
Walkthrough
The algorithm uses two auxiliary arrays: rowMins to store the minimum value of each row, and colMaxs to store the maximum value of each column.
First Pass: Iterate through the matrix to populate rowMins. For each row i, find its minimum element and store it in rowMins[i].
Second Pass: Iterate through the matrix again (or iterate column by column) to populate colMaxs. For each column j, find its maximum element and store it in colMaxs[j].
Final Check: After pre-computation, we have all the candidates. A number x is a lucky number if it's a minimum in some row i (x == rowMins[i]) and a maximum in some column j (x == colMaxs[j]). Since all matrix elements are distinct, we can simply find the intersection of the values in rowMins and colMaxs. To find the intersection efficiently, we can add all row minimums to a HashSet for O(1) average time lookups. Then, we iterate through the column maximums and check if each one exists in the set. If it does, it's a lucky number.
import java.util.ArrayList;import java.util.List;import java.util.HashSet;import java.util.Set; class Solution { public List<Integer> luckyNumbers (int[][] matrix) { int m = matrix.length; int n = matrix[0].length; int[] rowMins = new int[m]; for (int i = 0; i < m; i++) { int minVal = Integer.MAX_VALUE; for (int j = 0; j < n; j++) { minVal = Math.min(minVal, matrix[i][j]); } rowMins[i] = minVal; } int[] colMaxs = new int[n]; for (int j = 0; j < n; j++) { int maxVal = Integer.MIN_VALUE; for (int i = 0; i < m; i++) { maxVal = Math.max(maxVal, matrix[i][j]); } colMaxs[j] = maxVal; } List<Integer> luckyNumbers = new ArrayList<>(); Set<Integer> rowMinsSet = new HashSet<>(); for (int minVal : rowMins) { rowMinsSet.add(minVal); } for (int maxVal : colMaxs) { if (rowMinsSet.contains(maxVal)) { luckyNumbers.add(maxVal); } } return luckyNumbers; }}Complexity
Time
O(m * n). Populating `rowMins` takes `O(m*n)`. Populating `colMaxs` takes `O(m*n)`. Finding the intersection takes `O(m + n)`. The dominant term is `O(m*n)`.
Space
O(m + n). We use an array of size `m` for row minimums, an array of size `n` for column maximums, and a hash set of size up to `m`.
Trade-offs
Pros
Most efficient time complexity as it avoids redundant computations by processing rows and columns independently first.
Cons
Uses extra space proportional to the dimensions of the matrix.
This approach iterates through every single element of the matrix. For each element, it performs two checks: first, whether it's the minimum in its row, and second, whether it's the maximum in its column. If both conditions are met, the element is a "lucky number" and is added to the result list.
Algorithm
- Initialize an empty list
luckyNumbers. - Iterate through each row
ifrom 0 tom-1. - Iterate through each column
jfrom 0 ton-1. -
Let `candidate = matrix[i][j]`. -
Check if `candidate` is the minimum in row `i`. - Set a flag `isMinInRow = true`. - Iterate `k` from 0 to `n-1`. If `matrix[i][k] < candidate`, set `isMinInRow = false` and break. -
If `isMinInRow` is true, check if `candidate` is the maximum in column `j`. - Set a flag `isMaxInCol = true`. - Iterate `k` from 0 to `m-1`. If `matrix[k][j] > candidate`, set `isMaxInCol = false` and break. -
If `isMaxInCol` is also true, add `candidate` to `luckyNumbers`. - Return
luckyNumbers.
Walkthrough
The algorithm involves a nested loop to visit each cell (i, j) of the matrix. For each cell matrix[i][j], we first assume it's a lucky number. We then iterate through its entire row i to check if any other element matrix[i][k] is smaller than matrix[i][j]. If we find one, matrix[i][j] is not the row minimum, so it can't be a lucky number. We move to the next element in the matrix. If it is indeed the row minimum, we proceed to check its column j. We iterate through the entire column j to see if any other element matrix[k][j] is larger than matrix[i][j]. If we find one, it's not the column maximum and thus not a lucky number. Only if an element passes both the row minimum and column maximum checks is it added to the final list of lucky numbers.
import java.util.ArrayList;import java.util.List; class Solution { public List<Integer> luckyNumbers (int[][] matrix) { List<Integer> luckyNumbers = new ArrayList<>(); int m = matrix.length; int n = matrix[0].length; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { int candidate = matrix[i][j]; boolean isMinInRow = true; for (int k = 0; k < n; k++) { if (matrix[i][k] < candidate) { isMinInRow = false; break; } } if (isMinInRow) { boolean isMaxInCol = true; for (int k = 0; k < m; k++) { if (matrix[k][j] > candidate) { isMaxInCol = false; break; } } if (isMaxInCol) { luckyNumbers.add(candidate); } } } } return luckyNumbers; }}Complexity
Time
O(m * n * (m + n)). For each of the `m*n` elements, we scan its row (`n` elements) and potentially its column (`m` elements).
Space
O(1) auxiliary space, not counting the space for the output list.
Trade-offs
Pros
Simple to understand and implement.
Requires no extra data structures.
Cons
Highly inefficient, especially for large matrices, due to redundant computations.
Solutions
Solution
class Solution {public List<Integer> luckyNumbers(int[][] matrix) { int m = matrix.length, n = matrix[0].length; int[] rows = new int[m]; int[] cols = new int[n]; Arrays.fill(rows, 1 << 30); for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { rows[i] = Math.min(rows[i], matrix[i][j]); cols[j] = Math.max(cols[j], matrix[i][j]); } } List<Integer> ans = new ArrayList<>(); for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (rows[i] == cols[j]) { ans.add(rows[i]); } } } return ans; }}/** * @param {number[][]} matrix * @return {number[]} */ var luckyNumbers = function ( matrix ) { const m = matrix . length ; const n = matrix [ 0 ]. length ; const rows = new Array ( m ). fill ( 1 << 30 ); const cols = new Array ( n ). fill ( 0 ); for ( let i = 0 ; i < m ; ++ i ) { for ( let j = 0 ; j < n ; j ++ ) { rows [ i ] = Math . min ( rows [ i ], matrix [ i ][ j ]); cols [ j ] = Math . max ( cols [ j ], matrix [ i ][ j ]); } } const ans = []; for ( let i = 0 ; i < m ; ++ i ) { for ( let j = 0 ; j < n ; j ++ ) { if ( rows [ i ] === cols [ j ]) { ans . push ( rows [ i ]); } } } return ans ; };class Solution {public: vector<int> luckyNumbers(vector<vector<int>> &matrix) { int m = matrix.size(), n = matrix[0].size(); int rows[m]; int cols[n]; memset(rows, 0x3f, sizeof(rows)); memset(cols, 0, sizeof(cols)); for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { rows[i] = min(rows[i], matrix[i][j]); cols[j] = max(cols[j], matrix[i][j]); } } vector<int> ans; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (rows[i] == cols[j]) { ans.push_back(rows[i]); } } } return ans; }};class Solution: def luckyNumbers(self, matrix: List[List[int]]) -> List[int]: rows = {min(row) for row in matrix} cols = {max(col) for col in zip(* matrix)} return list(rows & cols)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.