0018. 4Sum¶
Table of Contents¶
- Problem
- Problem Breakdown
- Approach 1: Brute Force
- Approach 2: Sort + Two Pointers
- Complexity Comparison
- Edge Cases
- Common Mistakes
- Related Problems
- Visual Animation
Problem¶
| Leetcode | 18. 4Sum |
| Difficulty | |
| Tags | Array, Two Pointers, Sorting |
Description¶
Given an array
numsofnintegers, return an array of all the unique quadruplets[nums[a], nums[b], nums[c], nums[d]]such that:
0 <= a, b, c, d < na,b,c, anddare distinctnums[a] + nums[b] + nums[c] + nums[d] == targetYou may return the answer in any order.
Examples¶
Example 1:
Input: nums = [1,0,-1,0,-2,2], target = 0
Output: [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
Example 2:
Input: nums = [2,2,2,2,2], target = 8
Output: [[2,2,2,2]]
Constraints¶
1 <= nums.length <= 200-10^9 <= nums[i] <= 10^9-10^9 <= target <= 10^9
Problem Breakdown¶
1. What is being asked?¶
Find all unique quadruplets in the array that sum to a given target. Return the values (not indices), and there must be no duplicate quadruplets in the result.
2. What is the input?¶
| Parameter | Type | Description |
|---|---|---|
nums | int[] | Array of integers |
target | int | Target sum for the quadruplet |
Important observations about the input: - The array is unsorted (not sorted) - Duplicates may exist (Example 2: five 2 values) - The target can be negative, zero, or positive - Values can be very large (-10^9 to 10^9) — integer overflow risk - The array contains at least 1 element
3. What is the output?¶
- A list of quadruplets
[[a, b, c, d], ...] - Each quadruplet satisfies
a + b + c + d = target - No duplicate quadruplets (e.g.,
[-2,-1,1,2]should appear only once) - Order of quadruplets and order within a quadruplet do not matter
- Return an empty list if no quadruplets exist
4. Constraints analysis¶
| Constraint | Significance |
|---|---|
nums.length <= 200 | O(n^3) works (~8,000,000 operations), O(n^4) is borderline (~1.6 billion) |
-10^9 <= nums[i] <= 10^9 | Sum of four values can overflow int32! Must use int64/long |
-10^9 <= target <= 10^9 | Target itself fits in int32, but sum comparison needs int64 |
| No duplicate quadruplets | Must handle deduplication carefully |
5. Step-by-step example analysis¶
Example 1: nums = [1, 0, -1, 0, -2, 2], target = 0¶
After sorting: [-2, -1, 0, 0, 1, 2]
Fix i=0 (nums[i]=-2):
Fix j=1 (nums[j]=-1): target_remain = 0-(-2)-(-1) = 3
left=2 (0), right=5 (2): 0 + 2 = 2 < 3 → left++
left=3 (0), right=5 (2): 0 + 2 = 2 < 3 → left++
left=4 (1), right=5 (2): 1 + 2 = 3 == 3 ✅ → [-2, -1, 1, 2]
left=5 >= right=4 → stop
Fix j=2 (nums[j]=0): target_remain = 0-(-2)-0 = 2
left=3 (0), right=5 (2): 0 + 2 = 2 == 2 ✅ → [-2, 0, 0, 2]
left=4 (1), right=4, left >= right → stop
Fix j=3 (nums[j]=0): SKIP — duplicate of j=2
Fix j=4 (nums[j]=1): target_remain = 0-(-2)-1 = 1
left=5 (2), left >= right → stop
Fix i=1 (nums[i]=-1):
Fix j=2 (nums[j]=0): target_remain = 0-(-1)-0 = 1
left=3 (0), right=5 (2): 0 + 2 = 2 > 1 → right--
left=3 (0), right=4 (1): 0 + 1 = 1 == 1 ✅ → [-1, 0, 0, 1]
left=4 >= right=3 → stop
Fix j=3 (nums[j]=0): SKIP — duplicate of j=2
Fix i=2 (nums[i]=0): 0+0+1+2 = 3 > 0 → BREAK (early termination)
Result: [[-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1]]
Example 2: nums = [2, 2, 2, 2, 2], target = 8¶
After sorting: [2, 2, 2, 2, 2]
Fix i=0 (nums[i]=2):
Fix j=1 (nums[j]=2): target_remain = 8-2-2 = 4
left=2 (2), right=4 (2): 2 + 2 = 4 == 4 ✅ → [2, 2, 2, 2]
Skip duplicates → left=3, right=3, left >= right → stop
Fix j=2: SKIP — duplicate
Fix j=3: SKIP — duplicate
Fix i=1: SKIP — duplicate
Result: [[2, 2, 2, 2]]
6. Key Observations¶
- Extension of 3Sum — Fix two elements instead of one, then use Two Pointers for the remaining pair.
- Sorting enables deduplication — Identical values are adjacent, so skipping duplicates is easy at all four positions.
- Integer overflow — Sum of four values up to 10^9 can exceed int32 range (4 * 10^9 > 2.1 * 10^9). Must use int64/long.
- Early termination and pruning — Check min/max possible sums to skip unnecessary iterations.
7. Pattern identification¶
| Pattern | Why it fits | Example |
|---|---|---|
| Sort + Two Pointers | O(n^3), natural deduplication | 4Sum (this problem) |
| Hash Map | O(n^3), uses map for pair sums | Works but harder to deduplicate |
| Brute Force | Always works, but slow | All problems |
Chosen pattern: Sort + Two Pointers Reason: Sorting makes duplicate skipping trivial at all levels. Two Pointers gives O(n) for the innermost loop, yielding O(n^3) total. Pruning with early termination further improves practical performance.
Approach 1: Brute Force¶
Thought process¶
The simplest idea: check all possible quadruplets using four nested loops. Sort first so that duplicates can be detected by comparing with previous values. Use a set to avoid duplicate quadruplets.
Algorithm (step-by-step)¶
- Sort the array
- Four nested loops:
i,j = i+1,k = j+1,l = k+1 - If
nums[i] + nums[j] + nums[k] + nums[l] == target→ add to result (if not already seen) - Use a set of tuples for deduplication
Pseudocode¶
function fourSum(nums, target):
sort(nums)
result = []
seen = set()
for i = 0 to n-4:
for j = i+1 to n-3:
for k = j+1 to n-2:
for l = k+1 to n-1:
if nums[i] + nums[j] + nums[k] + nums[l] == target:
quad = (nums[i], nums[j], nums[k], nums[l])
if quad not in seen:
seen.add(quad)
result.append([nums[i], nums[j], nums[k], nums[l]])
return result
Complexity¶
| Complexity | Explanation | |
|---|---|---|
| Time | O(n^4) | Four nested loops checking all quadruplets |
| Space | O(n) | Set for storing seen quadruplets |
Implementation¶
Go¶
func fourSumBruteForce(nums []int, target int) [][]int {
sort.Ints(nums)
n := len(nums)
result := [][]int{}
seen := map[[4]int]bool{}
for i := 0; i < n-3; i++ {
for j := i + 1; j < n-2; j++ {
for k := j + 1; k < n-1; k++ {
for l := k + 1; l < n; l++ {
if int64(nums[i])+int64(nums[j])+int64(nums[k])+int64(nums[l]) == int64(target) {
quad := [4]int{nums[i], nums[j], nums[k], nums[l]}
if !seen[quad] {
seen[quad] = true
result = append(result, []int{nums[i], nums[j], nums[k], nums[l]})
}
}
}
}
}
}
return result
}
Java¶
public List<List<Integer>> fourSumBruteForce(int[] nums, int target) {
Arrays.sort(nums);
int n = nums.length;
Set<List<Integer>> resultSet = new LinkedHashSet<>();
for (int i = 0; i < n - 3; i++) {
for (int j = i + 1; j < n - 2; j++) {
for (int k = j + 1; k < n - 1; k++) {
for (int l = k + 1; l < n; l++) {
if ((long) nums[i] + nums[j] + nums[k] + nums[l] == target) {
resultSet.add(Arrays.asList(nums[i], nums[j], nums[k], nums[l]));
}
}
}
}
}
return new ArrayList<>(resultSet);
}
Python¶
def fourSumBruteForce(self, nums: list[int], target: int) -> list[list[int]]:
nums.sort()
n = len(nums)
result = []
seen = set()
for i in range(n - 3):
for j in range(i + 1, n - 2):
for k in range(j + 1, n - 1):
for l in range(k + 1, n):
if nums[i] + nums[j] + nums[k] + nums[l] == target:
quad = (nums[i], nums[j], nums[k], nums[l])
if quad not in seen:
seen.add(quad)
result.append([nums[i], nums[j], nums[k], nums[l]])
return result
Dry Run¶
Input: nums = [1, 0, -1, 0, -2, 2], target = 0
After sorting: [-2, -1, 0, 0, 1, 2]
i=0 (-2):
j=1 (-1):
k=2 (0):
l=3: -2-1+0+0=-3 ❌ l=4: -2-1+0+1=-2 ❌ l=5: -2-1+0+2=-1 ❌
k=3 (0):
l=4: -2-1+0+1=-2 ❌ l=5: -2-1+0+2=-1 ❌
k=4 (1):
l=5: -2-1+1+2=0 ✅ → [-2,-1,1,2]
j=2 (0):
k=3 (0):
l=4: -2+0+0+1=-1 ❌ l=5: -2+0+0+2=0 ✅ → [-2,0,0,2]
k=4 (1):
l=5: -2+0+1+2=1 ❌
j=3 (0):
k=4 (1):
l=5: -2+0+1+2=1 ❌
j=4 (1):
k=5: only one element left → skip
i=1 (-1):
j=2 (0):
k=3 (0):
l=4: -1+0+0+1=0 ✅ → [-1,0,0,1]
l=5: -1+0+0+2=1 ❌
k=4 (1):
l=5: -1+0+1+2=2 ❌
j=3 (0):
k=4 (1):
l=5: -1+0+1+2=2 ❌
i=2 (0):
j=3 (0):
k=4 (1):
l=5: 0+0+1+2=3 ❌
Total comparisons: 15 (for n=6)
Result: [[-2,-1,1,2], [-2,0,0,2], [-1,0,0,1]]
Approach 2: Sort + Two Pointers¶
The problem with Brute Force¶
O(n^4) is too slow for n = 200 (1.6 billion operations). We need to reduce the innermost search from O(n^2) to O(n).
Optimization idea¶
- Sort the array
- Fix two elements (
nums[i]andnums[j]), reducing the problem to Two Sum on a sorted subarray- Two Pointers (
left,right) converging inward to find pairs summing totarget - nums[i] - nums[j]- Skip duplicates at all four positions to avoid duplicate quadruplets
- Pruning — early termination and skipping when min/max sums are out of range
Algorithm (step-by-step)¶
- Sort the array
- For each
ifrom0ton-4: - If
i > 0andnums[i] == nums[i-1]→ skip (duplicate first element) - If
nums[i] + nums[i+1] + nums[i+2] + nums[i+3] > target→ break (min sum too large) - If
nums[i] + nums[n-3] + nums[n-2] + nums[n-1] < target→ continue (max sum too small) - For each
jfromi+1ton-3:- If
j > i+1andnums[j] == nums[j-1]→ skip (duplicate second element) - Apply similar pruning for j
- Set
left = j + 1,right = n - 1,remain = target - nums[i] - nums[j] - While
left < right: sum = nums[left] + nums[right]- If
sum == remain→ add quadruplet, skip duplicates, move both pointers - If
sum < remain→left++ - If
sum > remain→right--
- If
Pseudocode¶
function fourSum(nums, target):
sort(nums)
result = []
for i = 0 to n-4:
if i > 0 and nums[i] == nums[i-1]: continue
if nums[i]+nums[i+1]+nums[i+2]+nums[i+3] > target: break
if nums[i]+nums[n-3]+nums[n-2]+nums[n-1] < target: continue
for j = i+1 to n-3:
if j > i+1 and nums[j] == nums[j-1]: continue
if nums[i]+nums[j]+nums[j+1]+nums[j+2] > target: break
if nums[i]+nums[j]+nums[n-2]+nums[n-1] < target: continue
left = j + 1, right = n - 1
remain = target - nums[i] - nums[j]
while left < right:
sum = nums[left] + nums[right]
if sum == remain:
result.append([nums[i], nums[j], nums[left], nums[right]])
while left < right and nums[left] == nums[left+1]: left++
while left < right and nums[right] == nums[right-1]: right--
left++; right--
elif sum < remain:
left++
else:
right--
return result
Complexity¶
| Complexity | Explanation | |
|---|---|---|
| Time | O(n^3) | O(n log n) sort + O(n^2) outer loops * O(n) two-pointer = O(n^3) |
| Space | O(1) | In-place sorting, no extra data structures (output not counted) |
Implementation¶
Go¶
func fourSum(nums []int, target int) [][]int {
sort.Ints(nums)
n := len(nums)
result := [][]int{}
for i := 0; i < n-3; i++ {
if i > 0 && nums[i] == nums[i-1] { continue }
if int64(nums[i])+int64(nums[i+1])+int64(nums[i+2])+int64(nums[i+3]) > int64(target) { break }
if int64(nums[i])+int64(nums[n-3])+int64(nums[n-2])+int64(nums[n-1]) < int64(target) { continue }
for j := i + 1; j < n-2; j++ {
if j > i+1 && nums[j] == nums[j-1] { continue }
if int64(nums[i])+int64(nums[j])+int64(nums[j+1])+int64(nums[j+2]) > int64(target) { break }
if int64(nums[i])+int64(nums[j])+int64(nums[n-2])+int64(nums[n-1]) < int64(target) { continue }
left, right := j+1, n-1
remain := int64(target) - int64(nums[i]) - int64(nums[j])
for left < right {
sum := int64(nums[left]) + int64(nums[right])
if sum == remain {
result = append(result, []int{nums[i], nums[j], nums[left], nums[right]})
for left < right && nums[left] == nums[left+1] { left++ }
for left < right && nums[right] == nums[right-1] { right-- }
left++
right--
} else if sum < remain {
left++
} else {
right--
}
}
}
}
return result
}
Java¶
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
int n = nums.length;
List<List<Integer>> result = new ArrayList<>();
for (int i = 0; i < n - 3; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
if ((long) nums[i] + nums[i+1] + nums[i+2] + nums[i+3] > target) break;
if ((long) nums[i] + nums[n-3] + nums[n-2] + nums[n-1] < target) continue;
for (int j = i + 1; j < n - 2; j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
if ((long) nums[i] + nums[j] + nums[j+1] + nums[j+2] > target) break;
if ((long) nums[i] + nums[j] + nums[n-2] + nums[n-1] < target) continue;
int left = j + 1, right = n - 1;
long remain = (long) target - nums[i] - nums[j];
while (left < right) {
long sum = (long) nums[left] + nums[right];
if (sum == remain) {
result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
while (left < right && nums[left] == nums[left + 1]) left++;
while (left < right && nums[right] == nums[right - 1]) right--;
left++;
right--;
} else if (sum < remain) {
left++;
} else {
right--;
}
}
}
}
return result;
}
Python¶
def fourSum(self, nums: list[int], target: int) -> list[list[int]]:
nums.sort()
n = len(nums)
result = []
for i in range(n - 3):
if i > 0 and nums[i] == nums[i - 1]:
continue
if nums[i] + nums[i+1] + nums[i+2] + nums[i+3] > target:
break
if nums[i] + nums[n-3] + nums[n-2] + nums[n-1] < target:
continue
for j in range(i + 1, n - 2):
if j > i + 1 and nums[j] == nums[j - 1]:
continue
if nums[i] + nums[j] + nums[j+1] + nums[j+2] > target:
break
if nums[i] + nums[j] + nums[n-2] + nums[n-1] < target:
continue
left, right = j + 1, n - 1
remain = target - nums[i] - nums[j]
while left < right:
total = nums[left] + nums[right]
if total == remain:
result.append([nums[i], nums[j], nums[left], nums[right]])
while left < right and nums[left] == nums[left + 1]:
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1
elif total < remain:
left += 1
else:
right -= 1
return result
Dry Run¶
Input: nums = [1, 0, -1, 0, -2, 2], target = 0
After sorting: [-2, -1, 0, 0, 1, 2]
i=0, nums[i]=-2:
min_sum = -2-1+0+0 = -3 <= 0 ✓
max_sum = -2+0+1+2 = 1 >= 0 ✓
j=1, nums[j]=-1, remain = 0-(-2)-(-1) = 3:
left=2(0), right=5(2): 0+2=2 < 3 → left++
left=3(0), right=5(2): 0+2=2 < 3 → left++
left=4(1), right=5(2): 1+2=3 == 3 ✅ → [-2,-1,1,2]
left=5 >= right=4 → DONE
j=2, nums[j]=0, remain = 0-(-2)-0 = 2:
left=3(0), right=5(2): 0+2=2 == 2 ✅ → [-2,0,0,2]
left=4 >= right=4 → DONE
j=3, nums[j]=0: SKIP (duplicate, nums[3]==nums[2])
i=1, nums[i]=-1:
j=2, nums[j]=0, remain = 0-(-1)-0 = 1:
left=3(0), right=5(2): 0+2=2 > 1 → right--
left=3(0), right=4(1): 0+1=1 == 1 ✅ → [-1,0,0,1]
left=4 >= right=3 → DONE
j=3, nums[j]=0: SKIP (duplicate)
i=2, nums[i]=0: min_sum = 0+0+1+2 = 3 > 0 → BREAK
Result: [[-2,-1,1,2], [-2,0,0,2], [-1,0,0,1]]
Total operations: ~10 (vs 15 for Brute Force on this small input)
Complexity Comparison¶
| # | Approach | Time | Space | Pros | Cons |
|---|---|---|---|---|---|
| 1 | Brute Force | O(n^4) | O(n) | Simple logic | Too slow, TLE on large inputs |
| 2 | Sort + Two Pointers | O(n^3) | O(1) | Optimal, clean dedup, pruning | Requires sorting (modifies input) |
Which solution to choose?¶
- In an interview: Approach 2 (Sort + Two Pointers) — optimal and shows understanding of the kSum generalization
- In production: Approach 2 — best time complexity, minimal memory, pruning for practical speedup
- On Leetcode: Approach 2 — easily passes all test cases
- For learning: Both — Brute Force shows the problem clearly, Two Pointers shows the optimization from 3Sum extended to 4Sum
Edge Cases¶
| # | Case | Input | Target | Expected Output | Reason |
|---|---|---|---|---|---|
| 1 | LeetCode Example 1 | [1,0,-1,0,-2,2] | 0 | [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]] | Standard case with duplicates |
| 2 | All same values | [2,2,2,2,2] | 8 | [[2,2,2,2]] | Deduplication must produce only one quadruplet |
| 3 | All zeros | [0,0,0,0] | 0 | [[0,0,0,0]] | Only valid quadruplet is four zeros |
| 4 | Negative target | [-3,-2,-1,0,0,1,2,3] | -1 | 9 quadruplets | Many valid combinations |
| 5 | No valid quadruplets | [1,2,3,4,5] | 100 | [] | Target too large |
| 6 | Less than 4 elements | [1,2,3] | 6 | [] | Not enough elements |
| 7 | Large values (overflow) | [10^9,10^9,10^9,10^9] | -294967296 | [] | Must use int64 to avoid overflow |
| 8 | Empty array | [] | 0 | [] | No elements at all |
Common Mistakes¶
Mistake 1: Integer overflow¶
# In Java/Go, sum of four large values overflows int32!
# ❌ WRONG (Java)
if (nums[i] + nums[j] + nums[k] + nums[l] == target) # overflow!
# ✅ CORRECT (Java)
if ((long) nums[i] + nums[j] + nums[k] + nums[l] == target)
Reason: 10^9 * 4 = 4 * 10^9 exceeds int32 max (2.1 * 10^9). In Go, use int64() casts. In Python, this is not an issue since integers have arbitrary precision.
Mistake 2: Not skipping duplicates at all four positions¶
# ❌ WRONG — duplicate quadruplets in result
for i in range(n - 3):
for j in range(i + 1, n - 2):
# ...no duplicate skipping
# ✅ CORRECT — skip duplicates for i and j
for i in range(n - 3):
if i > 0 and nums[i] == nums[i - 1]: continue
for j in range(i + 1, n - 2):
if j > i + 1 and nums[j] == nums[j - 1]: continue
# ...also skip duplicates for left/right after finding a match
Reason: Without skipping at every level, the same quadruplet can be found multiple times.
Mistake 3: Wrong duplicate skip condition for j¶
# ❌ WRONG — skips valid combinations
if j > 0 and nums[j] == nums[j - 1]: continue
# ✅ CORRECT — only skip when j is not the first after i
if j > i + 1 and nums[j] == nums[j - 1]: continue
Reason: j > 0 would skip j = i+1 if nums[i+1] == nums[i], which is wrong because i and j are different positions. The condition must be j > i + 1.
Mistake 4: Missing pruning (not wrong, but slow)¶
# ❌ SLOWER — no pruning, checks unnecessary iterations
for i in range(n - 3):
if i > 0 and nums[i] == nums[i - 1]: continue
for j in range(i + 1, n - 2):
# ...
# ✅ FASTER — prune based on min/max possible sums
for i in range(n - 3):
if i > 0 and nums[i] == nums[i - 1]: continue
if nums[i] + nums[i+1] + nums[i+2] + nums[i+3] > target: break
if nums[i] + nums[n-3] + nums[n-2] + nums[n-1] < target: continue
# ...same pruning for j
Reason: Pruning dramatically reduces the number of iterations in practice. The break condition handles "all remaining too large" and the continue condition handles "even the largest possible sum is too small".
Related Problems¶
| # | Problem | Difficulty | Similarity |
|---|---|---|---|
| 1 | 1. Two Sum | Foundation — find two numbers summing to target | |
| 2 | 15. 3Sum | Same pattern with 3 elements — direct predecessor | |
| 3 | 16. 3Sum Closest | Closest sum variant with Sort + Two Pointers | |
| 4 | 167. Two Sum II - Sorted | Two Pointers on sorted array (innermost loop) | |
| 5 | 454. 4Sum II | Four separate arrays — hash map approach |
Visual Animation¶
Interactive animation: animation.html
In the animation: - Brute Force tab — four nested loops check all quadruplets - Two Pointers tab — sort + fix two elements + two pointers converging - Visualizes four pointer movement (i, j, left, right), duplicate skipping, and quadruplet collection