Facebook Pixel

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 most limits[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:

  1. The total number of selected elements doesn't exceed k
  2. The number of elements picked from each row i doesn't exceed limits[i]

Return this maximum sum.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. First considering the largest elements from each row (up to its limit)
  2. 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 row i)
  • 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 the k 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 limit limit:
    • Sort the row in ascending order using nums.sort()
    • Extract the largest limit elements from the sorted row

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 using heappop(pq)
    • This ensures we always keep only the k largest elements seen so far

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 Evaluator

Example 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() takes O(m log m) time where m is the number of columns
  • The inner loop runs at most limit times, which is bounded by m in the worst case
  • Within the inner loop:
    • nums.pop() takes O(1) time since we're popping from the end
    • heappush(pq, ...) takes O(log k) time as the heap size is maintained at most k
    • heappop(pq) takes O(log k) time when the heap exceeds size k
  • 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 most k 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)).

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

In a binary min heap, the minimum element can be found in:


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More