LeetCode 1439 - Find the Kth Smallest Sum of a Matrix With Sorted Rows
The problem requires finding the kth smallest sum obtainable by selecting exactly one element from each row of a matrix mat with sorted rows. Each row is sorted in non-decreasing order, and we must explore combinations of elements across rows to form sums.
Difficulty: 🔴 Hard
Topics: Array, Binary Search, Heap (Priority Queue), Matrix
Solution
Problem Understanding
The problem requires finding the kth smallest sum obtainable by selecting exactly one element from each row of a matrix mat with sorted rows. Each row is sorted in non-decreasing order, and we must explore combinations of elements across rows to form sums. The input is a 2D matrix of dimensions m x n and an integer k. The output is a single integer representing the kth smallest sum of all valid combinations.
Constraints tell us the matrix can have up to 40 rows and 40 columns (1 <= m, n <= 40), each element is positive and at most 5000, and k is bounded by min(200, n^m). This means brute-force generation of all possible combinations is impractical for large inputs because n^m can be enormous, but k is relatively small, allowing optimizations like priority queues or binary search over sums. Important edge cases include matrices with minimal dimensions (1x1), matrices where all elements are the same, and matrices where k is larger than the number of distinct sums.
Approaches
The brute-force approach generates all possible arrays by picking one element from each row, computes their sums, sorts the sums, and returns the kth smallest. While correct, this is inefficient because it requires generating n^m sums, which is computationally infeasible for large matrices.
The key observation for an optimal approach is that rows are sorted, so for any given row, the smallest sum combinations involve taking smaller elements first. This allows us to use a min-heap (priority queue) to efficiently generate the smallest sums in order without enumerating all possibilities. At each step, we expand the smallest sum in the heap by replacing an element in one row with the next element in that row, pushing the new sum into the heap, and maintaining only the k smallest sums. This is a classic application of a k-way merge using a min-heap.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n^m log(n^m)) | O(n^m) | Generate all sums, sort, and pick kth |
| Min-Heap Merge | O(m * k * log k) | O(k) | Merge rows progressively, keeping only k smallest sums |
Algorithm Walkthrough
- Initialize a min-heap with the first row of the matrix. This represents the smallest sums possible with only that row.
- Iterate over the remaining rows in the matrix. For each row, generate new candidate sums by adding each element of the current row to the sums in the heap from the previous step.
- Maintain a min-heap of size at most
kfor these new sums. This ensures we only keep track of the smallest possible sums. - After processing all rows, the min-heap contains the
ksmallest sums from all combinations. - Return the largest element in the heap if you maintain a max-heap for the top k sums, or pop from a min-heap
ktimes if using a standard min-heap.
Why it works: By merging rows iteratively and always considering only the next smallest elements due to the sorted property, the algorithm guarantees that the heap will always contain the k smallest sums after each merge. This maintains correctness without enumerating all possible sums.
Python Solution
from typing import List
import heapq
class Solution:
def kthSmallest(self, mat: List[List[int]], k: int) -> int:
# Initialize with the first row
heap = mat[0][:] # Make a copy
heapq.heapify(heap)
for i in range(1, len(mat)):
new_heap = []
row = mat[i]
for prev_sum in heap:
for num in row:
new_sum = prev_sum + num
heapq.heappush(new_heap, new_sum)
# Keep only the k smallest sums
heap = heapq.nsmallest(k, new_heap)
# The kth smallest sum
return heap[k - 1]
Implementation Walkthrough: We begin by copying the first row into a heap. For each subsequent row, we compute all possible sums of previous sums plus elements of the current row. To prevent the heap from growing too large, we keep only the k smallest sums at each stage using heapq.nsmallest. After all rows are merged, the kth element in the heap corresponds to the kth smallest sum.
Go Solution
import (
"container/heap"
"sort"
)
func kthSmallest(mat [][]int, k int) int {
heapSums := append([]int(nil), mat[0]...)
for i := 1; i < len(mat); i++ {
row := mat[i]
newHeap := []int{}
for _, prevSum := range heapSums {
for _, num := range row {
newHeap = append(newHeap, prevSum+num)
}
}
sort.Ints(newHeap)
if len(newHeap) > k {
newHeap = newHeap[:k]
}
heapSums = newHeap
}
return heapSums[k-1]
}
Go-specific notes: Since Go does not have a convenient nsmallest function like Python, we append all new sums into a slice, sort it, and truncate it to the first k elements. This maintains the same correctness invariant as the Python solution. We also make a copy of the first row to avoid modifying the input matrix.
Worked Examples
Example 1: mat = [[1,3,11],[2,4,6]], k = 5
Step by step:
- Start with heap
[1,3,11]. - Merge row
[2,4,6]:
[1+2, 1+4, 1+6, 3+2, 3+4, 3+6, 11+2, 11+4, 11+6] = [3,5,7,5,7,9,13,15,17]- Keep 5 smallest:
[3,5,5,7,7]
- The 5th smallest sum is
7.
Example 2: mat = [[1,3,11],[2,4,6]], k = 9
- All sums after merge:
[3,5,5,7,7,9,9,11,13,15,17] - The 9th smallest sum is
17.
Example 3: mat = [[1,10,10],[1,4,5],[2,3,6]], k = 7
- Merge row by row maintaining k smallest:
- Start
[1,10,10] - Merge
[1,4,5]→[2,5,6,11,14,15]→ keep 7 smallest[2,5,6,11,14,15] - Merge
[2,3,6]→[4,5,6,7,8,8,9,10,11,13,14,17,16,19,21,13,16,17]→ keep 7 smallest[4,5,5,6,6,7,8]
- 7th smallest sum:
9.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(m * k * n log k) | For each row, we combine k previous sums with n elements, keep only k smallest using heapq.nsmallest or sort+slice |
| Space | O(k) | Only k smallest sums are stored at any time |
The main savings comes from restricting attention to the k smallest sums instead of all possible sums, reducing exponential growth to manageable linear growth with respect to k.
Test Cases
# Provided examples
assert Solution().kthSmallest([[1,3,11],[2,4,6]], 5) == 7 # Example 1
assert Solution().kthSmallest([[1,3,11],[2,4,6]], 9) == 17 # Example 2
assert Solution().kthSmallest([[1,10,10],[1,4,5],[2,3,6]], 7) == 9 # Example 3
# Edge cases
assert Solution().kthSmallest([[1]], 1) == 1 # Single element
assert Solution().kthSmallest([[1,2],[3,4]], 4) == 6 # k equals total combinations
assert Solution().kthSmallest([[1,2,3],[1,2,3]], 5) == 5 # Multiple equal sums
| Test | Why |
|---|---|
| Example 1 | Basic correctness and merging of two rows |
| Example 2 | Larger k, tests heap truncation |
| Example 3 | Multiple rows and verifying correct sum order |
| Single element | Minimal input |
| k equals total combinations | Ensures algorithm handles upper bound of k |
| Multiple equal sums | Confirms algorithm handles duplicate sums correctly |
Edge Cases
- Single-row or single-column matrices: These are trivial, but if mishandled could break the heap logic. The implementation correctly initializes