2500. Delete Greatest Value in Each Row
Problem Description
You have a matrix grid
with m
rows and n
columns filled with positive integers. You need to perform a series of operations on this matrix until it becomes empty.
In each operation:
- Find and delete the largest value from each row (if there are multiple elements with the same maximum value in a row, you can delete any one of them)
- Among all the deleted elements from this operation, find the maximum value and add it to your running answer
After each operation, the matrix will have one fewer column since you're removing one element from each row.
For example, if you have a 3×3 matrix:
- In the first operation, you delete the maximum from each of the 3 rows, then add the largest of these 3 deleted values to your answer
- In the second operation, you work with a 3×2 matrix (one column removed), again delete the maximum from each row, and add the largest deleted value to your answer
- Continue until the matrix is empty
The solution approach sorts each row first. After sorting, the largest values in each row are aligned in the rightmost column, the second-largest values in the second-rightmost column, and so on. Then, by taking the maximum value from each column (using zip(*grid)
to transpose the matrix), we get the maximum deleted value for each operation. The sum of these maximum values gives us the final answer.
Intuition
The key insight is recognizing that we need to repeatedly find the maximum element from each row and then take the maximum among those deleted elements. Since we're going to delete elements column by column (one element from each row per operation), we need a way to organize the data to make this process efficient.
Think about what happens if we sort each row individually. After sorting, the largest element in each row will be at the rightmost position, the second-largest at the second-rightmost position, and so on. This arrangement perfectly aligns with our deletion pattern - we delete from largest to smallest in each row.
Once all rows are sorted, we can visualize the matrix differently. The last column now contains the largest element from each row (these will be deleted in the first operation). The second-to-last column contains the second-largest element from each row (deleted in the second operation), and so on.
To find the answer, we need the maximum value among elements deleted in each operation. Since elements deleted in the same operation are now in the same column after sorting, we can simply find the maximum value in each column and sum them up.
For example, if we have:
Original: [[5,1,3], [2,9,6]] After [sorting](/problems/sorting_summary): [[1,3,5], [2,6,9]]
Column-wise maximums: max(1,2)=2
, max(3,6)=6
, max(5,9)=9
Answer: 2 + 6 + 9 = 17
This transformation from a row-wise deletion problem to a column-wise maximum problem after sorting is the core insight that makes the solution elegant and efficient.
Learn more about Sorting and Heap (Priority Queue) patterns.
Solution Approach
The implementation follows a two-step approach that transforms the problem into a simpler one:
Step 1: Sort Each Row
for row in grid: row.sort()
We iterate through each row in the grid and sort it in ascending order. This in-place sorting operation ensures that:
- The smallest elements are positioned at the beginning of each row
- The largest elements are positioned at the end of each row
- Elements that will be deleted in the same operation are now aligned in the same column
Step 2: Calculate Column-wise Maximums and Sum
return sum(max(col) for col in zip(*grid))
This line performs multiple operations:
-
zip(*grid)
- The asterisk operator unpacks the grid rows andzip
transposes the matrix. This groups elements by column instead of by row. For example:- If
grid = [[1,3,5], [2,6,9]]
zip(*grid)
produces[(1,2), (3,6), (5,9)]
- If
-
max(col) for col in zip(*grid)
- For each column (which represents elements deleted in the same operation), we find the maximum value. This gives us the maximum deleted element for each operation. -
sum(...)
- Finally, we sum all these maximum values to get our answer.
The beauty of this approach is that it avoids simulating the actual deletion process. Instead of repeatedly finding and removing maximum elements, we reorganize the data once through sorting and then simply traverse the columns to compute the result. The time complexity is O(m * n * log(n))
for sorting m rows of n elements each, plus O(m * n)
for the final traversal.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with a 3×3 matrix to illustrate the solution approach.
Initial Grid:
grid = [[7, 2, 1], [6, 4, 2], [6, 5, 3]]
Step 1: Sort Each Row
We sort each row independently in ascending order:
grid = [[1, 2, 7], // sorted from [7, 2, 1] [2, 4, 6], // sorted from [6, 4, 2] [3, 5, 6]] // sorted from [6, 5, 3]
After sorting, notice how the elements are organized:
- Column 0: [1, 2, 3] - smallest elements from each row
- Column 1: [2, 4, 5] - middle elements from each row
- Column 2: [7, 6, 6] - largest elements from each row
Step 2: Find Column-wise Maximums
Using zip(*grid)
to transpose and get columns:
- Column 0: (1, 2, 3) → max = 3
- Column 1: (2, 4, 5) → max = 5
- Column 2: (7, 6, 6) → max = 7
These maximums represent:
- Operation 1: We delete 7, 6, 6 from original rows → max deleted = 7
- Operation 2: We delete 2, 4, 5 from remaining elements → max deleted = 5
- Operation 3: We delete 1, 2, 3 from remaining elements → max deleted = 3
Final Answer: Sum of maximums = 7 + 5 + 3 = 15
The key insight is that sorting aligns elements by their deletion order - the rightmost column contains elements deleted first (largest from each row), the next column contains elements deleted second, and so on. This transforms the row-wise deletion problem into a simple column-wise maximum calculation.
Solution Implementation
1class Solution:
2 def deleteGreatestValue(self, grid: List[List[int]]) -> int:
3 # Sort each row in ascending order
4 # This ensures that when we delete elements column by column,
5 # we're always removing the smallest remaining element from each row
6 for row in grid:
7 row.sort()
8
9 # Transpose the grid using zip(*grid) to get columns
10 # For each column, find the maximum value (which represents
11 # the greatest value deleted in that round)
12 # Sum all these maximum values to get the final result
13 return sum(max(col) for col in zip(*grid))
14
1class Solution {
2 public int deleteGreatestValue(int[][] grid) {
3 // Sort each row in ascending order
4 // This allows us to process columns from smallest to largest values
5 for (int[] row : grid) {
6 Arrays.sort(row);
7 }
8
9 // Initialize the sum of maximum values
10 int totalSum = 0;
11
12 // Iterate through each column index
13 for (int colIndex = 0; colIndex < grid[0].length; colIndex++) {
14 // Find the maximum value in the current column
15 int maxInColumn = 0;
16
17 // Check all rows for the current column
18 for (int rowIndex = 0; rowIndex < grid.length; rowIndex++) {
19 maxInColumn = Math.max(maxInColumn, grid[rowIndex][colIndex]);
20 }
21
22 // Add the maximum value of this column to the total sum
23 totalSum += maxInColumn;
24 }
25
26 return totalSum;
27 }
28}
29
1class Solution {
2public:
3 int deleteGreatestValue(vector<vector<int>>& grid) {
4 // Sort each row in ascending order
5 // This allows us to access the k-th largest element at position [i][n-k]
6 for (auto& row : grid) {
7 ranges::sort(row);
8 }
9
10 int totalSum = 0;
11 int numCols = grid[0].size();
12 int numRows = grid.size();
13
14 // Process each column from left to right
15 // After sorting, column j contains the (j+1)-th smallest elements from each row
16 for (int col = 0; col < numCols; ++col) {
17 int maxInColumn = 0;
18
19 // Find the maximum value in the current column
20 for (int row = 0; row < numRows; ++row) {
21 maxInColumn = max(maxInColumn, grid[row][col]);
22 }
23
24 // Add the maximum value of this column to the total sum
25 totalSum += maxInColumn;
26 }
27
28 return totalSum;
29 }
30};
31
1/**
2 * Deletes the greatest value from each row in multiple operations and returns the sum of maximum values
3 * @param grid - 2D array of numbers to process
4 * @returns Sum of maximum values from each deletion operation
5 */
6function deleteGreatestValue(grid: number[][]): number {
7 // Sort each row in ascending order
8 // This allows us to process columns from smallest to largest values
9 for (const row of grid) {
10 row.sort((a: number, b: number) => a - b);
11 }
12
13 let totalSum: number = 0;
14
15 // Process each column (representing each deletion operation)
16 for (let columnIndex: number = 0; columnIndex < grid[0].length; columnIndex++) {
17 let maxValueInColumn: number = 0;
18
19 // Find the maximum value in the current column across all rows
20 for (let rowIndex: number = 0; rowIndex < grid.length; rowIndex++) {
21 maxValueInColumn = Math.max(maxValueInColumn, grid[rowIndex][columnIndex]);
22 }
23
24 // Add the maximum value from this deletion operation to the total sum
25 totalSum += maxValueInColumn;
26 }
27
28 return totalSum;
29}
30
Time and Space Complexity
Time Complexity: O(m × n × log n)
The time complexity breaks down as follows:
- The outer loop iterates through each row in the grid, which takes
O(m)
iterations wherem
is the number of rows - For each row,
row.sort()
is called, which has a time complexity ofO(n log n)
wheren
is the number of columns (length of each row) - The
zip(*grid)
operation transposes the matrix inO(m × n)
time - The
sum(max(col) for col in zip(*grid))
iterates throughn
columns and finds the maximum ofm
elements in each column, takingO(m × n)
time
The dominant operation is sorting each row, so the overall time complexity is O(m × n × log n)
.
Space Complexity: O(log n)
The space complexity analysis:
- The sorting operation uses
O(log n)
space for the recursive call stack in Python's Timsort algorithm - The
zip(*grid)
creates an iterator that doesn't consume additional space proportional to the input - The generator expression
max(col) for col in zip(*grid)
processes one column at a time without storing all results - No additional data structures are created that scale with input size
Therefore, the space complexity is O(log n)
, dominated by the sorting algorithm's space requirements.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the Deletion Order
The Issue: A common misconception is thinking that we need to delete the globally largest element from the entire matrix in each operation. This leads to incorrect solutions where developers try to find the maximum across all rows first, then remove it.
What Actually Happens: We delete the largest element from each row independently in every operation, then only consider the maximum among those deleted elements for our answer.
Incorrect Approach Example:
# WRONG: Trying to find global maximum first
def deleteGreatestValue(self, grid):
result = 0
while grid and grid[0]:
max_val = max(max(row) for row in grid)
result += max_val
# Remove max_val from wherever it appears... (incorrect logic)
return result
Correct Understanding: The sorting approach works because it aligns elements by their relative rank within each row. After sorting, the rightmost column contains each row's maximum, ensuring we're deleting the correct elements.
Pitfall 2: Attempting In-Place Deletion Simulation
The Issue: Trying to simulate the actual deletion process by removing elements from the matrix after each operation, which leads to complex index management and potential errors.
Problems with This Approach:
- Removing elements from lists is O(n) operation
- Managing changing indices becomes error-prone
- Overall complexity increases unnecessarily
Inefficient Simulation Example:
# INEFFICIENT: Actually deleting elements
def deleteGreatestValue(self, grid):
result = 0
while grid[0]: # While columns remain
deleted_values = []
for row in grid:
max_val = max(row)
max_idx = row.index(max_val)
deleted_values.append(max_val)
row.pop(max_idx) # O(n) deletion
result += max(deleted_values)
return result
Better Solution: The sorting approach avoids actual deletion by pre-organizing the data, making the problem a simple column-wise maximum calculation.
Pitfall 3: Confusion with zip(*grid) Transposition
The Issue:
Not understanding how zip(*grid)
works or forgetting the unpacking operator *
, leading to incorrect column extraction.
Common Mistakes:
# WRONG: Missing unpacking operator
return sum(max(col) for col in zip(grid)) # This zips rows, not columns
# WRONG: Trying to manually transpose
columns = []
for j in range(len(grid[0])):
col = []
for i in range(len(grid)):
col.append(grid[i][j])
columns.append(col)
# This works but is unnecessarily verbose
Correct Usage:
The *
operator unpacks the grid so that each row becomes a separate argument to zip()
, which then pairs up elements by position, effectively transposing the matrix.
Pitfall 4: Not Sorting In-Place
The Issue: Creating new sorted arrays instead of sorting in-place, which uses extra space unnecessarily.
Space-Inefficient Approach:
def deleteGreatestValue(self, grid):
sorted_grid = []
for row in grid:
sorted_grid.append(sorted(row)) # Creates new list
return sum(max(col) for col in zip(*sorted_grid))
Optimal Approach:
def deleteGreatestValue(self, grid):
for row in grid:
row.sort() # Sorts in-place, O(1) extra space
return sum(max(col) for col in zip(*grid))
The in-place sorting maintains O(1) additional space complexity (excluding the space needed for sorting), while the alternative creates an entirely new grid with O(m*n) extra space.
How does quick sort divide the problem into subproblems?
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!