Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1439. Find the Kth Smallest Sum of a Matrix With Sorted Rows - Leetcode Solution

Code Implementation

import heapq

class Solution:
    def kthSmallest(self, mat, k):
        m, n = len(mat), len(mat[0])
        initial = sum(row[0] for row in mat)
        heap = [(initial, [0]*m)]
        visited = set(tuple([0]*m))
        
        for _ in range(k):
            curr_sum, indices = heapq.heappop(heap)
            for i in range(m):
                if indices[i] + 1 < n:
                    nxt = list(indices)
                    nxt[i] += 1
                    nxt_tuple = tuple(nxt)
                    if nxt_tuple not in visited:
                        new_sum = curr_sum - mat[i][indices[i]] + mat[i][nxt[i]]
                        heapq.heappush(heap, (new_sum, nxt))
                        visited.add(nxt_tuple)
        return curr_sum
      
#include <vector>
#include <queue>
#include <set>
using namespace std;

class Solution {
public:
    int kthSmallest(vector<vector<int>>& mat, int k) {
        int m = mat.size(), n = mat[0].size();
        vector<int> idx(m, 0);
        int initial = 0;
        for (int i = 0; i < m; ++i) initial += mat[i][0];
        typedef pair<int, vector<int>> Node;
        auto cmp = [](const Node& a, const Node& b) { return a.first > b.first; };
        priority_queue<Node, vector<Node>, decltype(cmp)> minHeap(cmp);
        set<vector<int>> visited;
        minHeap.push({initial, idx});
        visited.insert(idx);
        
        int curr_sum = 0;
        for (int t = 0; t < k; ++t) {
            auto [sum, indices] = minHeap.top(); minHeap.pop();
            curr_sum = sum;
            for (int i = 0; i < m; ++i) {
                if (indices[i] + 1 < n) {
                    vector<int> nxt = indices;
                    nxt[i]++;
                    if (!visited.count(nxt)) {
                        int new_sum = sum - mat[i][indices[i]] + mat[i][nxt[i]];
                        minHeap.push({new_sum, nxt});
                        visited.insert(nxt);
                    }
                }
            }
        }
        return curr_sum;
    }
};
      
import java.util.*;

class Solution {
    public int kthSmallest(int[][] mat, int k) {
        int m = mat.length, n = mat[0].length;
        int[] indices = new int[m];
        int initial = 0;
        for (int i = 0; i < m; ++i) initial += mat[i][0];
        PriorityQueue<Node> heap = new PriorityQueue<>();
        Set<String> visited = new HashSet<>();
        heap.offer(new Node(initial, indices.clone()));
        visited.add(Arrays.toString(indices));
        int curr_sum = 0;
        for (int t = 0; t < k; ++t) {
            Node node = heap.poll();
            curr_sum = node.sum;
            for (int i = 0; i < m; ++i) {
                if (node.indices[i] + 1 < n) {
                    int[] nxt = node.indices.clone();
                    nxt[i]++;
                    String key = Arrays.toString(nxt);
                    if (!visited.contains(key)) {
                        int new_sum = node.sum - mat[i][node.indices[i]] + mat[i][nxt[i]];
                        heap.offer(new Node(new_sum, nxt));
                        visited.add(key);
                    }
                }
            }
        }
        return curr_sum;
    }
    static class Node implements Comparable<Node> {
        int sum;
        int[] indices;
        Node(int s, int[] idx) {
            sum = s;
            indices = idx;
        }
        public int compareTo(Node other) {
            return Integer.compare(this.sum, other.sum);
        }
    }
}
      
class MinHeap {
    constructor() {
        this.heap = [];
    }
    push(item) {
        this.heap.push(item);
        this._bubbleUp(this.heap.length - 1);
    }
    pop() {
        if (this.heap.length === 1) return this.heap.pop();
        const top = this.heap[0];
        this.heap[0] = this.heap.pop();
        this._bubbleDown(0);
        return top;
    }
    _bubbleUp(idx) {
        while (idx > 0) {
            let parent = (idx - 1) >> 1;
            if (this.heap[parent][0] <= this.heap[idx][0]) break;
            [this.heap[parent], this.heap[idx]] = [this.heap[idx], this.heap[parent]];
            idx = parent;
        }
    }
    _bubbleDown(idx) {
        const length = this.heap.length;
        while (true) {
            let left = 2 * idx + 1, right = 2 * idx + 2, smallest = idx;
            if (left < length && this.heap[left][0] < this.heap[smallest][0]) smallest = left;
            if (right < length && this.heap[right][0] < this.heap[smallest][0]) smallest = right;
            if (smallest === idx) break;
            [this.heap[smallest], this.heap[idx]] = [this.heap[idx], this.heap[smallest]];
            idx = smallest;
        }
    }
    size() { return this.heap.length; }
}

var kthSmallest = function(mat, k) {
    const m = mat.length, n = mat[0].length;
    let initial = 0;
    for (let i = 0; i < m; ++i) initial += mat[i][0];
    const heap = new MinHeap();
    const visited = new Set();
    const start = Array(m).fill(0);
    heap.push([initial, start]);
    visited.add(start.join(','));
    let curr_sum = 0;
    for (let t = 0; t < k; ++t) {
        const [sum, indices] = heap.pop();
        curr_sum = sum;
        for (let i = 0; i < m; ++i) {
            if (indices[i] + 1 < n) {
                const nxt = indices.slice();
                nxt[i]++;
                const key = nxt.join(',');
                if (!visited.has(key)) {
                    let new_sum = sum - mat[i][indices[i]] + mat[i][nxt[i]];
                    heap.push([new_sum, nxt]);
                    visited.add(key);
                }
            }
        }
    }
    return curr_sum;
};
      

Problem Description

Given a matrix mat of size m x n, where each row of mat is sorted in non-decreasing order, you are asked to find the k-th smallest sum that can be formed by picking exactly one element from each row and summing them up.

Key constraints:

  • You must pick exactly one element from each row.
  • Rows are sorted, but you may pick any element from a row.
  • Each combination consists of one element per row, and combinations may overlap in sum but must be generated from different index choices.
  • Return the k-th smallest such sum (1-based indexing).
  • Do not reuse the same index from the same row in a combination.

Thought Process

At first glance, the problem may seem like a variant of finding the k-th smallest element in a sorted array, but here, the "elements" are sums generated by picking one value from each row. Since each row is sorted, we can try to exploit this property.

The brute-force approach would be to generate all possible combinations (which is n^m if there are n columns and m rows), compute their sums, sort them, and pick the k-th smallest. However, this is computationally infeasible for larger matrices.

Instead, we look for a more efficient way, possibly using a min-heap (priority queue) to always expand the next smallest sum, similar to the approach used in "Kth Smallest Element in a Sorted Matrix". By always expanding the next smallest sum, we can avoid generating all combinations up front and only focus on the most promising candidates.

Solution Approach

The solution uses a min-heap (priority queue) to efficiently generate the next smallest possible sum by picking one element from each row. Here’s a step-by-step breakdown:

  1. Initialization:
    • Start with the smallest possible sum, which is picking the first element from every row (since rows are sorted).
    • Keep track of the indices chosen for each row (initially all zeros).
    • Push this starting sum and its indices into the min-heap.
    • Use a set to record which index combinations have already been visited to avoid duplicates.
  2. Heap Expansion:
    • Repeatedly pop the smallest sum from the heap.
    • For each row, try to increment the index (i.e., move to the next element in that row), creating a new combination.
    • If this new combination of indices hasn't been seen before, calculate the new sum and push it into the heap.
    • Mark the new combination as visited.
  3. Repeat:
    • Repeat this process k times. The sum popped from the heap on the k-th iteration is the answer.

This approach is efficient because it always expands the next smallest sum and avoids recomputing or revisiting the same index combinations.

Example Walkthrough

Suppose:
mat = [[1,3,11], [2,4,6], [5,7,8]]
k = 5

  1. Start: The smallest sum is 1 + 2 + 5 = 8 (indices [0,0,0]).
  2. Expand: For each row, increment its index (if possible):
    • Row 0: [1,0,0] → sum = 3 + 2 + 5 = 10
    • Row 1: [0,1,0] → sum = 1 + 4 + 5 = 10
    • Row 2: [0,0,1] → sum = 1 + 2 + 7 = 10
    All three have sum 10, push them into the heap.
  3. Pop next smallest: All have sum 10.
    • Pop one (say [1,0,0]), expand it:
      • Row 0: [2,0,0] → sum = 11 + 2 + 5 = 18
      • Row 1: [1,1,0] → sum = 3 + 4 + 5 = 12
      • Row 2: [1,0,1] → sum = 3 + 2 + 7 = 12
  4. Continue: Each time, pop the smallest sum, expand by incrementing an index, and push new sums if not already seen.
  5. After 5 pops: The sums in order: 8, 10, 10, 10, 12. So the answer is 12.

This process ensures we always expand the next smallest possible sum, efficiently reaching the k-th smallest.

Time and Space Complexity

Brute-force approach:

  • Time: O(n^m \cdot m) (where n is number of columns, m is number of rows), since all n^m combinations are generated and summed.
  • Space: O(n^m) for storing all sums before sorting.
Optimized (Heap-based) approach:
  • Time: O(k \cdot m \cdot \log k), since at most k elements are pushed/popped from the heap, and for each, up to m new combinations are tried.
  • Space: O(k) for the heap and the visited set.

The heap-based approach is efficient even for reasonably large k and matrix sizes because it avoids expanding all combinations.

Summary

This problem is a classic example of using a priority queue (min-heap) to efficiently generate and expand only the most promising candidates, leveraging the sorted property of each row. Instead of brute-forcing all combinations, we always expand the next smallest sum and use a visited set to avoid redundant work. This approach is both elegant and practical, making it a valuable technique for similar "k-th smallest combination" problems.