Problem Overview — LeetCode 18 4Sum
LeetCode 18 asks you to find all unique quadruplets in an integer array nums that sum to a given target. The output is a list of lists — order within each quadruplet does not matter, but no duplicate quadruplets are allowed in the result.
The brute-force approach of checking every combination of four indices runs in O(n^4), which is far too slow for n up to 200. The optimal solution reduces the problem layer by layer, arriving at an O(n^3) algorithm using sorting and two pointers.
Key constraints to keep in mind: the array length is 1 to 200, values range from -10^9 to 10^9, and the target can also be as large as 10^9 in absolute value. The wide value range means integer overflow is a real concern in Java and C++.
- Array length: 1 <= nums.length <= 200
- Value range: -10^9 <= nums[i] <= 10^9
- Target range: -10^9 <= target <= 10^9
- Output: list of unique quadruplets summing to target
- No duplicate quadruplets allowed in the answer
From 2Sum to kSum — The Reduction Pattern
2Sum is the base case: given a sorted array and a target, use two pointers from both ends — left and right — and move them inward based on whether the current sum is too small or too large. This finds all pairs in O(n).
3Sum extends this by fixing one element with an outer loop, then applying 2Sum on the remaining suffix. For each fixed element nums[i], you solve the 2Sum on nums[i+1..n-1] with target - nums[i]. This gives O(n^2) total.
4Sum extends 3Sum by adding another outer loop. Fix nums[i] and nums[j], then solve the inner 2Sum on nums[j+1..n-1] with target - nums[i] - nums[j]. Two nested loops plus one O(n) two-pointer pass equals O(n^3).
k-Sum Pattern
Sort the array. Fix k-2 elements with nested loops, then solve the innermost 2Sum with two pointers. Total time: O(n^(k-1)). For 4Sum that is O(n^3); for 3Sum it is O(n^2). The same code structure scales to any k.
4Sum Implementation — Nested Loops and Two Pointers
Start by sorting the array. Sorting is required for two pointers to work and also makes it trivial to skip duplicate values. Once sorted, the outer loop index i runs from 0 to n-4, and the inner loop index j runs from i+1 to n-3.
For every (i, j) pair, set left = j+1 and right = n-1. The two-pointer target for this inner search is remain = target - nums[i] - nums[j]. If nums[left] + nums[right] == remain, you have found a quadruplet; append it and advance both pointers. If the sum is too small, advance left. If too large, retreat right.
The outer two loops cover all O(n^2) pairs, and each pair triggers an O(n) two-pointer scan, giving the overall O(n^3) complexity. Space complexity is O(1) excluding the output list.
- 1Sort nums in non-decreasing order
- 2Outer loop: i from 0 to n-4
- 3Inner loop: j from i+1 to n-3
- 4Set left = j+1, right = n-1, remain = target - nums[i] - nums[j]
- 5Two-pointer: if sum == remain, save quadruplet and move both pointers; if sum < remain, increment left; else decrement right
- 6Skip duplicates at every level after processing
Duplicate Skipping — Ensuring Unique Quadruplets
Because the array is sorted, duplicate values are adjacent. After processing a value at position i, skip all subsequent positions where nums[i] == nums[i-1]. The same rule applies at each of the four pointer positions independently.
For the outer loop i: at the start of each iteration (when i > 0), skip if nums[i] == nums[i-1]. For the inner loop j: when j > i+1, skip if nums[j] == nums[j-1]. For the two pointers after recording a result: advance left while nums[left] == nums[left-1], and retreat right while nums[right] == nums[right+1].
The critical rule is to skip duplicates AFTER processing the first occurrence, not before. If you skip at the beginning of the loop without checking whether you already processed that value, you will miss valid quadruplets that happen to start with a repeated element.
Duplicate Logic
Duplicate skipping at each level is identical to 3Sum. The key is: skip AFTER processing, not before. Check nums[i] == nums[i-1] at i > 0; check nums[j] == nums[j-1] at j > i+1. Missing this distinction is the most common source of wrong answers.
Early Termination — Pruning for Speed
Two pruning conditions applied at the outer loop (i) can cut runtime significantly on nearly-sorted or bounded inputs. First: if nums[i] * 4 > target, break entirely — since the array is sorted, every element from i onward is at least nums[i], so no quadruplet starting here or later can be small enough. Second: if nums[i] + nums[n-1] * 3 < target, continue — the best possible quadruplet using nums[i] as the smallest element is still below target, so skip this i.
Apply the same two pruning conditions at the inner loop (j): if nums[i] + nums[j] * 3 > target, break; if nums[i] + nums[j] + nums[n-1] * 2 < target, continue. These four pruning checks add no asymptotic cost but dramatically reduce the practical number of two-pointer passes on typical inputs.
Together, the pruning turns a worst-case O(n^3) algorithm into something that runs noticeably faster on real LeetCode test cases with duplicate-heavy or extreme-value inputs.
- 1At i: if nums[i] * 4 > target → break (all future sums too large)
- 2At i: if nums[i] + nums[n-1] * 3 < target → continue (best sum using this i is too small)
- 3At j: if nums[i] + nums[j] * 3 > target → break inner loop
- 4At j: if nums[i] + nums[j] + nums[n-1] * 2 < target → continue inner loop
Code Walkthrough — Python and Java
In Python, the implementation is clean and concise. Sort the array, run two nested for loops for i and j with duplicate-skip guards, then run the two-pointer inner loop. Python integers are arbitrary precision so there is no overflow concern. The entire solution is under 25 lines.
In Java, the structure is identical but you must cast to long before summing: long sum = (long)nums[i] + nums[j] + nums[left] + nums[right]. Without the cast, nums[i] + nums[j] is computed as 32-bit int and can silently overflow before the comparison. This is a common source of wrong answers that passes all small test cases but fails on edge inputs.
Both implementations share the same structure: sort, nested loops with pruning and duplicate skipping, two-pointer inner loop with result collection. Time complexity O(n^3), space complexity O(1) excluding output. On LeetCode 18, this passes all 295 test cases.
Integer Overflow in Java
In Java and C++, always cast to long before summing four large values. Use: long sum = (long)nums[i] + nums[j] + nums[left] + nums[right]. Without the cast, the intermediate sum nums[i] + nums[j] can overflow a 32-bit int before the addition of nums[left] and nums[right], silently producing wrong answers.