3462. Maximum Sum With at Most K Elements
Problem Description
You have a 2D matrix grid
with n
rows and m
columns containing integers. You also have an array limits
with n
elements and an integer k
.
Your task is to select at most k
elements from the matrix to maximize their sum, with one constraint:
- From row
i
(0-indexed), you can pick at mostlimits[i]
elements.
For example, if limits[0] = 2
, you can select 0, 1, or 2 elements from the first row, but not more than 2.
The goal is to find the maximum possible sum by strategically choosing which elements to pick from each row, ensuring:
- The total number of selected elements doesn't exceed
k
- The number of elements picked from each row
i
doesn't exceedlimits[i]
Return this maximum sum.
Intuition
To maximize the sum, we want to select the largest possible elements from the entire matrix. The key insight is that we should greedily pick the largest available elements, but we're constrained by two factors: the total count k
and the per-row limits.
Think of it this way: if we could pick any k
elements without row restrictions, we'd simply take the k
largest elements from the entire matrix. However, the row limits add complexity - we can't just take all large elements from one row if that exceeds its limit.
The solution cleverly handles this by:
- First considering the largest elements from each row (up to its limit)
- Then keeping only the
k
largest among all these candidates
For each row, we sort the elements and take the largest ones (up to the row's limit). By adding these to a min-heap and maintaining its size at most k
, we automatically keep track of the k
largest elements seen so far. The min-heap property ensures that when we exceed k
elements, we can efficiently remove the smallest one (at the heap's root).
This approach works because:
- We never violate row limits (we only take up to
limits[i]
from rowi
) - We end up with the
k
largest elements among all valid candidates - The min-heap efficiently maintains the "top k" elements as we process each row
The beauty of this solution is that it transforms a complex constraint problem into a simple "find the k largest elements" problem by pre-filtering candidates based on row limits.
Learn more about Greedy, Sorting and Heap (Priority Queue) patterns.
Solution Approach
The implementation uses a greedy approach combined with a min-heap to efficiently find the maximum sum:
Step 1: Initialize a Min-Heap
- Create an empty priority queue
pq
that will maintain thek
largest elements
Step 2: Process Each Row
- Iterate through each row along with its corresponding limit using
zip(grid, limits)
- For each row
nums
with limitlimit
:- Sort the row in ascending order using
nums.sort()
- Extract the largest
limit
elements from the sorted row
- Sort the row in ascending order using
Step 3: Maintain Top K Elements
- For each of the
limit
largest elements from the current row:- Pop the largest element from the sorted row using
nums.pop()
(since the row is sorted ascending, the last element is the largest) - Push this element into the min-heap using
heappush(pq, nums.pop())
- If the heap size exceeds
k
, remove the smallest element usingheappop(pq)
- This ensures we always keep only the
k
largest elements seen so far
- Pop the largest element from the sorted row using
Step 4: Calculate the Result
- After processing all rows, the heap contains at most
k
elements - These are the largest valid elements we can select
- Return their sum using
sum(pq)
Why This Works:
- By sorting each row and taking from the end, we ensure we're considering the largest elements from each row
- The min-heap automatically maintains the
k
largest elements across all rows - When we pop from the min-heap (when size >
k
), we're removing the smallest among our candidates, keeping the larger ones - The row limits are naturally respected since we only take up to
limit
elements from each row
Time Complexity: O(n * m * log(m) + n * limit * log(k))
where sorting each row takes O(m * log(m))
and heap operations take O(log(k))
Space Complexity: O(k)
for the heap
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Given:
grid = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
limits = [1, 2, 1]
k = 4
We want to select at most 4 elements total, with at most 1 from row 0, at most 2 from row 1, and at most 1 from row 2.
Step 1: Initialize min-heap
pq = []
(empty heap to maintain top k elements)
Step 2: Process Row 0 (limits[0] = 1)
- Row:
[1, 2, 3]
- Sort:
[1, 2, 3]
- Take largest 1 element:
3
- Add to heap:
pq = [3]
- Heap size (1) ≤ k (4), so keep all elements
Step 3: Process Row 1 (limits[1] = 2)
- Row:
[4, 5, 6]
- Sort:
[4, 5, 6]
- Take largest 2 elements:
6, 5
- Add
6
to heap:pq = [3, 6]
- Add
5
to heap:pq = [3, 6, 5]
→ heap structure:[3, 6, 5]
- Heap size (3) ≤ k (4), so keep all elements
Step 4: Process Row 2 (limits[2] = 1)
- Row:
[7, 8, 9]
- Sort:
[7, 8, 9]
- Take largest 1 element:
9
- Add
9
to heap:pq = [3, 6, 5, 9]
→ heap structure:[3, 6, 5, 9]
- Heap size (4) ≤ k (4), so keep all elements
Step 5: Calculate Result
- Final heap contains:
[3, 6, 5, 9]
- These are the 4 largest valid elements we can select
- Sum = 3 + 6 + 5 + 9 = 23
Verification:
- We picked element 3 from row 0 (1 element, respects limit of 1)
- We picked elements 5, 6 from row 1 (2 elements, respects limit of 2)
- We picked element 9 from row 2 (1 element, respects limit of 1)
- Total elements picked: 4 (respects k = 4)
This gives us the maximum possible sum of 23 while respecting all constraints.
Solution Implementation
1import heapq
2from typing import List
3
4class Solution:
5 def maxSum(self, grid: List[List[int]], limits: List[int], k: int) -> int:
6 # Min heap to maintain the k largest elements
7 min_heap = []
8
9 # Process each row with its corresponding limit
10 for row, limit in zip(grid, limits):
11 # Sort the current row in ascending order
12 row.sort()
13
14 # Take up to 'limit' largest elements from this row
15 for _ in range(limit):
16 # Pop the largest element from the sorted row (last element)
17 largest_element = row.pop()
18
19 # Add to heap
20 heapq.heappush(min_heap, largest_element)
21
22 # Maintain heap size at most k by removing the smallest element
23 if len(min_heap) > k:
24 heapq.heappop(min_heap)
25
26 # Return the sum of k largest elements across all rows
27 return sum(min_heap)
28
1class Solution {
2 /**
3 * Finds the maximum sum of k elements selected from a 2D grid with row constraints.
4 *
5 * @param grid 2D array where each row contains integers
6 * @param limits Array specifying the maximum number of elements that can be selected from each row
7 * @param k Total number of elements to select
8 * @return Maximum possible sum of k selected elements
9 */
10 public long maxSum(int[][] grid, int[] limits, int k) {
11 // Min heap to maintain the k largest elements encountered
12 PriorityQueue<Integer> minHeap = new PriorityQueue<>();
13
14 int numRows = grid.length;
15
16 // Process each row of the grid
17 for (int rowIndex = 0; rowIndex < numRows; rowIndex++) {
18 int[] currentRow = grid[rowIndex];
19 int maxSelectableFromRow = limits[rowIndex];
20
21 // Sort current row in ascending order to easily access largest elements
22 Arrays.sort(currentRow);
23
24 // Select up to 'maxSelectableFromRow' largest elements from current row
25 for (int j = 0; j < maxSelectableFromRow; j++) {
26 // Add elements from the end (largest elements after sorting)
27 int element = currentRow[currentRow.length - j - 1];
28 minHeap.offer(element);
29
30 // Maintain only k largest elements in the heap
31 if (minHeap.size() > k) {
32 minHeap.poll(); // Remove the smallest element
33 }
34 }
35 }
36
37 // Calculate sum of all elements remaining in the heap
38 long totalSum = 0;
39 for (int element : minHeap) {
40 totalSum += element;
41 }
42
43 return totalSum;
44 }
45}
46
1class Solution {
2public:
3 long long maxSum(vector<vector<int>>& grid, vector<int>& limits, int k) {
4 // Min-heap to maintain the k largest elements across all rows
5 priority_queue<int, vector<int>, greater<int>> minHeap;
6
7 int numRows = grid.size();
8
9 // Process each row of the grid
10 for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) {
11 // Copy current row to avoid modifying the original grid
12 vector<int> currentRow = grid[rowIndex];
13 int maxSelectableFromRow = limits[rowIndex];
14
15 // Sort the current row in ascending order
16 ranges::sort(currentRow);
17
18 // Select up to 'maxSelectableFromRow' largest elements from this row
19 for (int j = 0; j < maxSelectableFromRow; ++j) {
20 // Add elements from the end (largest elements after sorting)
21 int elementIndex = currentRow.size() - j - 1;
22 minHeap.push(currentRow[elementIndex]);
23
24 // Maintain heap size at most k by removing the smallest element
25 if (minHeap.size() > k) {
26 minHeap.pop();
27 }
28 }
29 }
30
31 // Calculate the sum of all elements remaining in the heap
32 long long totalSum = 0;
33 while (!minHeap.empty()) {
34 totalSum += minHeap.top();
35 minHeap.pop();
36 }
37
38 return totalSum;
39 }
40};
41
1/**
2 * Finds the maximum sum of k elements selected from a 2D grid with row-wise limits
3 * @param grid - 2D array where each row contains numbers to select from
4 * @param limits - Array specifying maximum number of elements that can be selected from each row
5 * @param k - Total number of elements to select
6 * @returns Maximum possible sum of k selected elements
7 */
8function maxSum(grid: number[][], limits: number[], k: number): number {
9 // Min heap to maintain the k largest elements
10 const minHeap = new MinPriorityQueue();
11 const rowCount = grid.length;
12
13 // Process each row of the grid
14 for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
15 const currentRow = grid[rowIndex];
16 const maxSelectableFromRow = limits[rowIndex];
17
18 // Sort current row in ascending order
19 currentRow.sort((a, b) => a - b);
20
21 // Select up to 'maxSelectableFromRow' largest elements from current row
22 for (let elementIndex = 0; elementIndex < maxSelectableFromRow; elementIndex++) {
23 // Add elements from the end (largest values after sorting)
24 const elementToAdd = currentRow[currentRow.length - elementIndex - 1];
25 minHeap.enqueue(elementToAdd);
26
27 // Maintain heap size of at most k elements (keeping k largest)
28 if (minHeap.size() > k) {
29 minHeap.dequeue();
30 }
31 }
32 }
33
34 // Calculate sum of all elements remaining in the heap
35 let totalSum = 0;
36 while (!minHeap.isEmpty()) {
37 totalSum += minHeap.dequeue() as number;
38 }
39
40 return totalSum;
41}
42
Time and Space Complexity
Time Complexity: O(n × m × (log m + log k))
The time complexity analysis breaks down as follows:
- The outer loop iterates through
n
rows of the grid - For each row,
nums.sort()
takesO(m log m)
time wherem
is the number of columns - The inner loop runs at most
limit
times, which is bounded bym
in the worst case - Within the inner loop:
nums.pop()
takesO(1)
time since we're popping from the endheappush(pq, ...)
takesO(log k)
time as the heap size is maintained at mostk
heappop(pq)
takesO(log k)
time when the heap exceeds sizek
- Therefore, for each row:
O(m log m) + O(m × log k) = O(m × (log m + log k))
- Total complexity:
O(n × m × (log m + log k))
Space Complexity: O(k)
The space complexity is determined by:
- The priority queue
pq
maintains at mostk
elements throughout the execution - The sorting operation on each row is done in-place
- No other significant auxiliary space is used
- Therefore, the space complexity is
O(k)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Modifying the Original Grid
The current implementation modifies the input grid
by sorting and popping elements from each row. This is destructive and can cause issues if:
- The original grid needs to be preserved for other operations
- The function is called multiple times with the same grid
- The grid is used elsewhere in the program
Solution: Create a copy of each row before sorting and popping:
def maxSum(self, grid: List[List[int]], limits: List[int], k: int) -> int:
min_heap = []
for row, limit in zip(grid, limits):
# Create a copy to avoid modifying the original
row_copy = row.copy()
row_copy.sort()
# Take up to 'limit' largest elements from this row
for _ in range(min(limit, len(row_copy))):
largest_element = row_copy.pop()
heapq.heappush(min_heap, largest_element)
if len(min_heap) > k:
heapq.heappop(min_heap)
return sum(min_heap)
Pitfall 2: IndexError When Row Has Fewer Elements Than Limit
If a row has fewer elements than its corresponding limit (e.g., row has 3 elements but limit is 5), the code will crash with an IndexError when trying to pop from an empty list.
Solution:
Use min(limit, len(row))
to ensure we don't try to pop more elements than available:
# Take up to 'limit' largest elements, but not more than row size
elements_to_take = min(limit, len(row_copy))
for _ in range(elements_to_take):
largest_element = row_copy.pop()
# ... rest of the code
Pitfall 3: Inefficient Element Selection
Sorting the entire row is unnecessary when we only need the largest limit
elements. For large matrices where limit
is much smaller than the row size, this creates unnecessary overhead.
Solution:
Use heapq.nlargest()
to efficiently get only the required elements:
def maxSum(self, grid: List[List[int]], limits: List[int], k: int) -> int:
min_heap = []
for row, limit in zip(grid, limits):
# Efficiently get the largest 'limit' elements without full sort
largest_elements = heapq.nlargest(min(limit, len(row)), row)
for element in largest_elements:
heapq.heappush(min_heap, element)
if len(min_heap) > k:
heapq.heappop(min_heap)
return sum(min_heap)
This approach has better time complexity when limit << m
: O(n * m + n * limit * log(limit) + n * limit * log(k))
instead of O(n * m * log(m) + n * limit * log(k))
.
In a binary min heap, the minimum element can be found in:
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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
Want a Structured Path to Master System Design Too? Don’t Miss This!