3Sum
Problem
nums, list every distinct triplet of values
[a, b, c], taken from three different positions, with
a + b + c = 0. Two triplets with the same values (in any order) count once.
The output order does not matter.[-1,0,1,2,-1,-4]Output[[-1,-1,2],[-1,0,1]][0,1,1]Output[][0,0,0]Output[[0,0,0]]3 ≤ n ≤ 3000 · -105 ≤ nums[i] ≤ 105 · no duplicate triplets
Try it yourself
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
}
}
nums[i]: the rest is
Two Sum II on the sorted suffix with target -nums[i]. Sorting also makes
duplicates adjacent, so they are skipped with simple neighbour checks.Instinct Sorted input, a pair/triple hitting a target, or something symmetric? Put one pointer at each end and move the one that can only improve things.
Memory hack Squeeze from both ends - every step throws away a whole row of the brute-force grid.
Open the Two Pointers guide →Approaches
Brute ForceAll Triplets + Set of Sorted Triplets
O(n3)Space O(n2)Check every index triple i < j < k. To drop duplicates,
sort each matching triplet and store it in a HashSet (at most
O(n2) distinct triplets exist, since two values fix the third).
public List<List<Integer>> threeSum(int[] nums) { Set<List<Integer>> triplets = new HashSet<>(); int n = nums.length; for (int i = 0; i < n - 2; i++) for (int j = i + 1; j < n - 1; j++) for (int k = j + 1; k < n; k++) if (nums[i] + nums[j] + nums[k] == 0) { List<Integer> t = Arrays.asList(nums[i], nums[j], nums[k]); Collections.sort(t); triplets.add(t); } return new ArrayList<>(triplets); }
Correct but cubic: ~4.5·109 checks at n = 3000.
BetterSort + HashSet Two Sum per i
O(n2)Space O(n)Sort, fix nums[i] (skipping repeated values of i),
then scan j to the right with a seen set: if
-nums[i]-nums[j] was seen, we have a triplet. After a hit, skip equal
nums[j] so the same pair isn't reported twice.
public List<List<Integer>> threeSum(int[] nums) { Arrays.sort(nums); List<List<Integer>> res = new ArrayList<>(); for (int i = 0; i < nums.length - 2; i++) { if (i > 0 && nums[i] == nums[i - 1]) continue; Set<Integer> seen = new HashSet<>(); for (int j = i + 1; j < nums.length; j++) { int need = -nums[i] - nums[j]; if (seen.contains(need)) { res.add(Arrays.asList(nums[i], need, nums[j])); while (j + 1 < nums.length && nums[j] == nums[j + 1]) j++; } seen.add(nums[j]); } } return res; }
Quadratic, but hashing costs extra memory and constant factor.
OptimalSort + Two Pointers
O(n2)Space O(log n)Sort, fix nums[i], and run two pointers l = i+1,
r = n-1: sum < 0 → l++, sum > 0 →
r--. On a hit, record it and step both pointers past equal values. If
nums[i] > 0, no later triplet can reach 0 — stop. (Space excludes
the output; O(log n) is the sort's stack.)
public List<List<Integer>> threeSum(int[] nums) { Arrays.sort(nums); List<List<Integer>> result = new ArrayList<>(); for (int i = 0; i < nums.length - 2 && nums[i] <= 0; i++) { if (i > 0 && nums[i] == nums[i - 1]) continue; // same first value int left = i + 1, right = nums.length - 1; while (left < right) { int sum = nums[i] + nums[left] + nums[right]; if (sum < 0) left++; else if (sum > 0) right--; else { result.add(Arrays.asList(nums[i], nums[left], nums[right])); while (left < right && nums[left] == nums[left + 1]) left++; while (left < right && nums[right] == nums[right - 1]) right--; left++; right--; } } } return result; }
Dry runsorted: [-4,-1,-1,0,1,2]
- i=0-4i-1l-1012r-3 < 0 → l++ (to l=r)
- i=1-4-1i-1l012r0 → add [-1,-1,2]
- i=1-4-1i-10l1r20 → add [-1,0,1]
- i=2-4-1-1i012dup of i=1 → skip
- i=3-4-1-10i1l2r3 > 0 → r-- → done ✔
Same O(n2) bound, no hashing, natural de-duplication — the standard answer.
At a glance
| Level | Approach | Time | Space |
|---|---|---|---|
| Brute Force | All Triplets + Set of Sorted Triplets | O(n3) | O(n2) |
| Better | Sort + HashSet Two Sum per i | O(n2) | O(n) |
| Optimal | Sort + Two Pointers | O(n2) | O(log n) |
- Forgetting to skip duplicate
nums[i]— emits[-1,0,1]twice. - Skipping duplicates before recording a hit loses
[-1,-1,2]. - Missing
left < rightin the skip loops — index out of bounds.
- Say it out loud: “3Sum = sort + loop + Two Sum II”.
- O(n2) is the accepted bound; sorting's O(n log n) is dominated.
- Generalise: kSum recurses down to the same two-pointer core.