Editorial
Core insight
Find K Pairs with Smallest Sums · Heap (Priority Queue) Patterns
Core Insight for Find K Pairs with Smallest Sums
The key intuition relies on the sorted nature of the input arrays.
Consider nums1 = [u1, u2, u3] and nums2 = [v1, v2, v3].
We can visualize the sums as a matrix where row corresponds to nums1[i] combined with every element of nums2:
- Row 0:
(u1+v1), (u1+v2), (u1+v3), ... - Row 1:
(u2+v1), (u2+v2), (u2+v3), ... - Row 2:
(u3+v1), (u3+v2), (u3+v3), ...
Because nums2 is sorted, every row in this virtual matrix is strictly non-decreasing. (u_i + v_1) \le (u_i + v_2) \le (u_i + v_3).
This structure transforms the problem into finding the smallest elements from sorted lists (rows).
Visual Description: Imagine a frontier of pointers. Initially, the smallest possible sums for each row are at index 0 (the first column). The absolute smallest sum must be among these first elements. Once we pick the smallest sum from a specific row at index , the next candidate from that specific row is at index . The Min-Heap maintains the head of these "active" rows, allowing us to efficiently extract the global minimum and advance the pointer for that specific row only.
Deep dive
Full breakdown
Full reasoning, pitfalls, and implementation flow
LeetCode 373: Find K Pairs with Smallest Sums Solution & Explanation
Problem Overview
TL;DR: The optimal solution treats the problem as merging sorted lists using a Min-Heap to efficiently select the smallest sums without generating all pairs.
The problem asks us to find the pairs with the smallest sums formed by picking one integer from array nums1 and one from array nums2. Both input arrays are sorted in non-decreasing order. This is a classic optimization problem found in interviews, often referred to as LeetCode 373. The goal is to return these pairs efficiently, avoiding the computational cost of processing every possible combination.
Brute Force Approach for Find K Pairs with Smallest Sums
The most straightforward approach is to generate every possible pair where and , calculate their sums, and find the smallest .
Naive Algorithm Steps
- Initialize an empty list
all_pairs. - Iterate through every element in
nums1. - For each , iterate through every element in
nums2. - Append the pair
[nums1[i], nums2[j]]toall_pairs. - Sort
all_pairsbased on the sum of elements. - Return the first elements.
Pseudo-code
pairs = []
for u in nums1:
for v in nums2:
pairs.append([u, v])
sort pairs by (u + v)
return pairs[0 to k]Analysis of Failure
- Time Complexity: , where and are the lengths of
nums1andnums2. Since constraints allow lengths up to , can reach . Sorting this many elements is computationally infeasible. - Space Complexity: to store all pairs. This will immediately cause a Memory Limit Exceeded error on large inputs.
- Why it fails: This approach ignores the critical property that the input arrays are already sorted. It performs redundant work by calculating large sums that have no chance of being in the top .
Algorithm Strategy: Heap (Priority Queue) Patterns
We utilize a Min-Heap to maintain the "frontier" of candidates.
- Initialization: Instead of adding all pairs, we only initialize the heap with the first pair from each "row". Specifically, we push
(nums1[i] + nums2[0], i, 0)for the first elements ofnums1. We only need the first rows because the element atnums1[k](combined withnums2[0]) is guaranteed to be larger than any of the first pairs ifnums1is sorted. - Tracking State: The heap stores tuples containing
(sum, index_in_nums1, index_in_nums2). The sum determines the priority. - Iterative Extraction:
- Extract the element with the smallest sum from the heap. Let's say this corresponds to indices .
- Add this pair to our result list.
- Expand: If there is a next element in that specific row (i.e.,
nums2has an element at ), calculate the new sumnums1[i] + nums2[j+1]and push(new_sum, i, j+1)back into the heap.
- Termination: Repeat until we have collected pairs or the heap is empty.
This strategy enforces that we only generate and compare candidates that are plausibly the next smallest, keeping the heap size small.
Execution Flow
- Validation: Check if
nums1ornums2is empty or if . Return empty list if so. - Heap Setup: Create a Min-Heap. Iterate from to . Push
(nums1[i] + nums2[0], i, 0)onto the heap. - Selection Loop: While the result list size is less than and the heap is not empty:
- Pop the top element:
(current_sum, i, j). - Append
[nums1[i], nums2[j]]to the result list. - Check if .
- If valid, push
(nums1[i] + nums2[j+1], i, j+1)into the heap.
- Pop the top element:
- Return: Return the accumulated result list.
Proof of Correctness
The correctness relies on the invariant that the heap always contains the smallest unvisited element from every active row.
- Initially, the heap contains the minimum element of every relevant row (the first column).
- The global minimum must be the root of the Min-Heap.
- When we remove
(nums1[i], nums2[j]), the next smallest element in row is(nums1[i], nums2[j+1]). - Since
nums2is sorted,nums1[i] + nums2[j+1] \ge nums1[i] + nums2[j]. - By adding this successor to the heap, we ensure the heap continues to hold the "head" of every sorted stream. Therefore, successive pops yield the globally smallest sums in order.
Pattern Reuse Notes
The Heap - K-way Merge pattern used in LeetCode 373 is highly versatile. Understanding this solution helps solve:
- LeetCode 23: Merge k Sorted Lists - The canonical version of this pattern where the input is explicit linked lists rather than implicit array combinations.
- LeetCode 378: Kth Smallest Element in a Sorted Matrix - Identical logic. The rows of the matrix are the sorted lists.
- LeetCode 632: Smallest Range Covering Elements from K Lists - Uses the same heap structure to maintain a window across sorted lists.
Feynman coach · Before you peek at the code
Can you explain the core insight in your own words?
If you can explain it before reading code, you’re far more likely to recall it under interview pressure.
Reference Implementation
C++ Solution for LeetCode 373
1#include <vector>
2#include <queue>
3#include <tuple>
4
5using namespace std;
6
7class Solution {
8public:
9 vector<vector<int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2, int k) {
10 vector<vector<int>> result;
11 if (nums1.empty() || nums2.empty() || k == 0) {
12 return result;
13 }
14
15 // Min-heap stores tuples: {sum, index_in_nums1, index_in_nums2}
16 // Utilizing greater<> for min-heap behavior
17 auto comp = [&](const tuple<int, int, int>& a, const tuple<int, int, int>& b) {
18 return get<0>(a) > get<0>(b);
19 };
20 priority_queue<tuple<int, int, int>, vector<tuple<int, int, int>>, decltype(comp)> minHeap(comp);
21
22 // Initialize heap with the first element of the first k rows
23 // We only need to consider the first k elements of nums1
24 for (int i = 0; i < nums1.size() && i < k; ++i) {
25 minHeap.push({nums1[i] + nums2[0], i, 0});
26 }
27
28 while (k-- > 0 && !minHeap.empty()) {
29 auto [sum, i, j] = minHeap.top();
30 minHeap.pop();
31
32 result.push_back({nums1[i], nums2[j]});
33
34 // If there is a next element in the current row (nums1[i]), push it
35 if (j + 1 < nums2.size()) {
36 minHeap.push({nums1[i] + nums2[j + 1], i, j + 1});
37 }
38 }
39
40 return result;
41 }
42};Java Solution for LeetCode 373
1import java.util.ArrayList;
2import java.util.List;
3import java.util.PriorityQueue;
4
5class Solution {
6 public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
7 List<List<Integer>> result = new ArrayList<>();
8 if (nums1.length == 0 || nums2.length == 0 || k == 0) {
9 return result;
10 }
11
12 // Min-heap stores int array: [sum, index_nums1, index_nums2]
13 PriorityQueue<int[]> minHeap = new PriorityQueue<>((a, b) -> a[0] - b[0]);
14
15 // Initialize heap with the first element of the first k rows from nums1
16 // combined with nums2[0]
17 for (int i = 0; i < nums1.length && i < k; i++) {
18 minHeap.offer(new int[]{nums1[i] + nums2[0], i, 0});
19 }
20
21 while (k > 0 && !minHeap.isEmpty()) {
22 int[] current = minHeap.poll();
23 int i = current[1];
24 int j = current[2];
25
26 List<Integer> pair = new ArrayList<>();
27 pair.add(nums1[i]);
28 pair.add(nums2[j]);
29 result.add(pair);
30
31 // Push the next element from the same row (increment index in nums2)
32 if (j + 1 < nums2.length) {
33 minHeap.offer(new int[]{nums1[i] + nums2[j + 1], i, j + 1});
34 }
35
36 k--;
37 }
38
39 return result;
40 }
41}Python Solution for LeetCode 373
1import heapq
2
3class Solution:
4 def kSmallestPairs(self, nums1: list[int], nums2: list[int], k: int) -> list[list[int]]:
5 if not nums1 or not nums2 or k == 0:
6 return []
7
8 # Min-heap stores tuples: (sum, index_nums1, index_nums2)
9 min_heap = []
10
11 # Initialize heap with the first element of the first k rows
12 # We pair nums1[i] with nums2[0]
13 for i in range(min(k, len(nums1))):
14 heapq.heappush(min_heap, (nums1[i] + nums2[0], i, 0))
15
16 result = []
17
18 while k > 0 and min_heap:
19 current_sum, i, j = heapq.heappop(min_heap)
20 result.append([nums1[i], nums2[j]])
21
22 # If there is a next element in the current row (nums1[i] + nums2[j+1])
23 if j + 1 < len(nums2):
24 heapq.heappush(min_heap, (nums1[i] + nums2[j + 1], i, j + 1))
25
26 k -= 1
27
28 return result