4. 4Sum
Question
Given an array nums of integers, return an array of all unique quadruplets [nums[a], nums[b], nums[c], nums[d]] such that the four indices are distinct and their corresponding values sum exactly to target.
Solution
Pattern
Sort + Fix Two + Two Pointers (Generalized -Sum) Sort the array. Use nested loops to mathematically fix the first two elements. Reduce the remaining unsorted portion to a standard "2Sum II" problem, solving it with opposing pointers (left and right).
How to Identify
- The problem asks for exact combinations of a fixed size () that satisfy a sum constraint.
- The prompt strictly forbids duplicate combinations, strongly hinting at sorting.
- The need to return values rather than original indices allows you to sort the array without penalty.
Description
Step-by-step explanation:
- Sort: Sort the input array in ascending order. This enables both duplicate skipping and the two-pointer directional logic.
- Fix (Outer Loop): Iterate from to .
- Skip if
nums[i] == nums[i-1]to prevent duplicate quadruplets. - Prune: If , break. The current and all future numbers are too large.
- Prune: If nums[i] + \text{last 3 elements} < target, continue. is too small to ever reach the target.
- Skip if
- Fix (Inner Loop): Iterate from to .
- Skip if
nums[j] == nums[j-1](ensure j > i+1). - Apply similar pruning logic tailored for and the remaining 2 elements.
- Skip if
- Two Pointers: Set and .
- Search: While left < right:
- Calculate
sum = nums[i] + nums[j] + nums[left] + nums[right]. (Uselongto prevent overflow!) - If
sum == target, record the quadruplet, move both pointers inward, and usewhileloops to skip any duplicate values forleftandright. - If
sum < target, incrementleft. - If
sum > target, decrementright.
- Calculate
The Intuition
This is the logical extension of the 3Sum problem. If 3Sum reduces a 3D problem into a 2D problem by fixing one variable, 4Sum reduces a 4D problem into a 2D problem by fixing two variables. By sorting the array, we create a landscape where we can methodically lock in the first two numbers ( and ). Once locked, we only need to find two numbers ( and ) that equal . Because the remaining array is sorted, we can elegantly find and using a sliding window of opposing pointers. The sorting also allows us to mathematically prove when to stop searching entirely (pruning).
Complexity
| Label | Worst | Average |
|---|---|---|
| Time Complexity | ||
| Space Complexity |
Time Complexity
Sorting takes . Fixing takes . Fixing takes . The while loop processes the remaining elements in . The total time is , which mathematically simplifies to .
Space Complexity
We do not allocate auxiliary data structures. However, sorting algorithms (like Java's Quicksort variant) require to recursive stack space depending on the array's initial state.
Code
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
if (nums == null || nums.length < 4) return new ArrayList<>();
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
int n = nums.length;
for (int i = 0; i < n - 3; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue; // Deduplicate i
// Mathematical Pruning
if ((long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) break;
if ((long) nums[i] + nums[n - 1] + nums[n - 2] + nums[n - 3] < target) continue;
for (int j = i + 1; j < n - 2; j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) continue; // Deduplicate j
// Mathematical Pruning
if ((long) nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) break;
if ((long) nums[i] + nums[j] + nums[n - 1] + nums[n - 2] < target) continue;
int left = j + 1;
int right = n - 1;
while (left < right) {
long sum = (long) nums[i] + nums[j] + nums[left] + nums[right];
if (sum == target) {
res.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
left++;
right--;
while (left < right && nums[left] == nums[left - 1]) left++;
while (left < right && nums[right] == nums[right + 1]) right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
}
}
return res;
}
}Caveats
- Integer Overflow: A massive trap in this problem. The sum of four 32-bit integers can easily exceed
Integer.MAX_VALUE. You must cast the first element tolongduring the sum calculation to force Java to promote the entire addition to 64-bit space. - Hash Map Sub-optimality: You can technically achieve average time by calculating all pairs and storing them in a Hash Map, then doing a second pass to find pairs . However, deduplicating the results is a nightmare, requiring a
HashSetof sorted strings or lists, which causes massive constant-factor overhead and space complexity. For strict deduplication, Sort + Two Pointers is the enterprise standard.
Concepts to Think About
- Recursion for -Sum: This code structure scales to 5Sum, 6Sum, etc., but writing nested loops manually is bad engineering. For a generic -Sum problem, you should write a recursive function that fixes one variable and calls -Sum, bottoming out at the 2Sum Two-Pointer logic.
- Pruning Power: The early exit
ifstatements might look like premature optimization, but on LeetCode's skewed test cases, they can reduce an algorithm's real-world runtime from 20ms to 2ms. - Overflow: Always use
longwhen summing multiple integers in a target problem. - Generalization: Could you write a generic kSum function that uses recursion to handle any k?
- The Two-Pointer Direction: Why does left++ increase the sum and right-- decrease it? (Only because the array is sorted).
Logical Follow-up
Question: How would you solve 4Sum II (Given four integer arrays nums1, nums2, nums3, nums4, return the number of tuples (i, j, k, l) such that nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0)?
Solution: Because the elements come from four separate arrays, deduplication is based on indices, not values. We can group the arrays into two pairs. We compute all possible sums of nums1[i] + nums2[j] and store their frequencies in a Hash Map. Then, we compute all possible sums of nums3[k] + nums4[l]. For each sum, we check if its negation exists in the Hash Map and add the frequency to our total count. Time: , Space: .
Question: "How would you implement a general kSum(int[] nums, int target, int k) function?"
Solution: Use recursion.
- Base Case: If , use the Two-Pointer approach ().
- Recursive Step: Iterate from
starttoend. For each element, callkSumfor the sub-problem:kSum(nums, target - nums[i], k - 1). - Remember to skip duplicates at every level of the recursion.