3 Sum
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given an integer array nums. Return every distinct triplet of values [a, b, c] taken from three different positions such that a + b + c == 0. Two triplets are the same if they contain the same three values, regardless of order or of which indices produced them, so each triplet should appear once. Report each triplet in non-decreasing order; the order of the triplets themselves does not matter.
Constraints: 0 ≤ n ≤ 3000, values fit in a 32-bit signed integer.
Example 1
nums = [-1, 0, 1, 2, -1, -4]
answer = [[-1, -1, 2], [-1, 0, 1]]
-1 + -1 + 2 = 0 and -1 + 0 + 1 = 0. The triplet [0, 1, -1] is the same set of values as [-1, 0, 1], so it is not listed twice.
Example 2
nums = [0, 0, 0, 0]
answer = [[0, 0, 0]]
Four zeros give four different index triples, but all of them are the same value triplet.
Example 3
nums = [0, 1, 1]
answer = []
No three values add up to zero.
Approach 1: Brute force
Intuition. The statement is “some three elements sum to zero”, so enumerate every triple of indices i < j < k. The only wrinkle is uniqueness: the same value triplet can come from many index triples, so sort each found triplet and drop it into a Set, which silently discards repeats.
Algorithm.
- For every
i, everyj > i, everyk > j: ifnums[i] + nums[j] + nums[k] == 0, sort the three values and add them to a set. - Return the set’s contents (sorted here only so tests can compare approaches).
static List<List<Integer>> bruteForce(int[] nums) {
int n = nums.length;
Set<List<Integer>> set = new HashSet<>();
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
for (int k = j + 1; k < n; k++) {
if ((long) nums[i] + nums[j] + nums[k] == 0) {
int[] t = {nums[i], nums[j], nums[k]};
Arrays.sort(t);
set.add(List.of(t[0], t[1], t[2]));
}
}
}
}
return sorted(set);
}
Complexity. Time O(n^3): about n^3 / 6 triples, each checked in constant time (the set insert is O(1) on average for 3-element lists). Space O(k) for the k distinct triplets in the set.
The (long) cast is not decoration: three values near 10^9 add up to 3 × 10^9, outside the int range, and a wrapped sum could accidentally become 0.
For n = 3000 this is 4.5 × 10^9 triple checks, far too slow. We need to stop paying for the third loop.
Approach 2: Better, hashing the third value
Intuition. Once two values nums[i] and nums[j] are fixed, the third one is forced: it must be -(nums[i] + nums[j]). Instead of searching for it with a loop, ask a hash set “have you seen this value?” This is exactly the trick from Two Sum, applied once per fixed i.
To avoid counting the same index triple three times, and to avoid pairing an element with itself, keep the set restricted to the values strictly between i and j. Fix i, start with an empty set, and as j moves right, check the set for the needed value and only then insert nums[j]. Every index triple i < k < j is then discovered exactly once, when the outer index is i and the scanning index is j.
Algorithm.
- For each
i, create an empty setseen. - For each
j > i: computeneed = -(nums[i] + nums[j]).- If
needis inseen, sort{nums[i], nums[j], need}and add it to the result set. - Insert
nums[j]intoseen.
- If
- Return the result set.
static List<List<Integer>> better(int[] nums) {
int n = nums.length;
Set<List<Integer>> set = new HashSet<>();
for (int i = 0; i < n; i++) {
Set<Integer> seen = new HashSet<>(); // values nums[i+1 .. j-1]
for (int j = i + 1; j < n; j++) {
long need = -(long) nums[i] - nums[j]; // third value that would complete the triplet
if (need >= Integer.MIN_VALUE && need <= Integer.MAX_VALUE && seen.contains((int) need)) {
int[] t = {nums[i], nums[j], (int) need};
Arrays.sort(t);
set.add(List.of(t[0], t[1], t[2]));
}
seen.add(nums[j]);
}
}
return sorted(set);
}
Complexity. Time O(n^2): two nested loops with O(1) average hash operations inside. Space O(n) for seen, plus the output set.
need is computed as a long because -(nums[i] + nums[j]) can leave the int range; a value outside that range cannot be in the array, so we skip the lookup entirely.
The downside is constant factors and memory: two hash structures, boxing of every value, and hashing of every triplet. The optimal approach removes all of that.
Approach 3: Optimal, sort and two pointers
Intuition. Sort the array. Fix the smallest element of the triplet, a[i]. The remaining two must come from a[i+1 .. n-1] and sum to -a[i]. In a sorted range, a pair with a given sum is found by two pointers: lo at the left end, hi at the right end. If a[i] + a[lo] + a[hi] is too small, only moving lo right can increase it; if too large, only moving hi left can decrease it. Each step retires one element, so a fixed i costs O(n).
Sorting also makes de-duplication free. Equal values are adjacent, so after handling a[i] we skip to the next different value for i; and after recording a triplet we move lo and hi past any copies of the values just used. No set is needed, and the output comes out already in lexicographic order.
Algorithm.
- Sort a copy of the array.
- For each
ifrom0ton - 3:- If
a[i] == a[i-1], skip (this first value was processed already). - If
a[i] > 0, stop: all remaining values are positive, their sum cannot be zero. - Set
lo = i + 1,hi = n - 1. Whilelo < hi:sum = a[i] + a[lo] + a[hi]. Ifsum < 0,lo++. Ifsum > 0,hi--.- Otherwise record
[a[i], a[lo], a[hi]], move both pointers inward, and then skip over duplicates ofa[lo-1]anda[hi+1].
- If
- Return the list.
static List<List<Integer>> optimal(int[] nums) {
int[] a = nums.clone();
Arrays.sort(a);
int n = a.length;
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < n - 2; i++) {
if (i > 0 && a[i] == a[i - 1]) continue; // same first value was already handled
if (a[i] > 0) break; // three positives can never sum to zero
int lo = i + 1, hi = n - 1;
while (lo < hi) {
long sum = (long) a[i] + a[lo] + a[hi];
if (sum < 0) lo++;
else if (sum > 0) hi--;
else {
res.add(List.of(a[i], a[lo], a[hi]));
lo++;
hi--;
while (lo < hi && a[lo] == a[lo - 1]) lo++; // skip equal second values
while (lo < hi && a[hi] == a[hi + 1]) hi--; // skip equal third values
}
}
}
return res;
}
Dry run on nums = [-1, 0, 1, 2, -1, -4], sorted to a = [-4, -1, -1, 0, 1, 2]:
| i | a[i] | lo, hi | sum | action |
|---|---|---|---|---|
| 0 | -4 | 1, 5 | -3 | lo++ (repeats until lo meets hi; nothing found) |
| 1 | -1 | 2, 5 | 0 | record [-1, -1, 2]; lo=3, hi=4 |
| 1 | -1 | 3, 4 | 0 | record [-1, 0, 1]; lo=4, hi=3, stop |
| 2 | -1 | a[2] == a[1], skip | ||
| 3 | 0 | 4, 5 | 3 | hi– then lo == hi, stop |
Result: [[-1, -1, 2], [-1, 0, 1]].
Complexity. Time O(n^2): O(n log n) to sort, then n choices of i each with an O(n) two-pointer sweep. Space O(1) extra beyond the sorted copy and the output (sorting in place would make it truly O(1)).
Why does skipping duplicates not lose answers? Suppose a[lo] == a[lo+1] after a hit. Any triplet using a[lo+1] with the same a[i] would need a third value equal to the one just paired with a[lo], so it is the same value triplet. The same argument applies to i: if a[i] == a[i-1], every triplet starting at i was reachable from i - 1 with a strictly larger range.
Edge cases and pitfalls
- Fewer than three elements.
n < 3must return an empty list; the loop boundi < n - 2handles it, includingn = 0. - Duplicates.
[0, 0, 0, 0]yields one triplet, not four. Approaches 1 and 2 rely on the set; approach 3 relies on the skip loops. Forgetting theiskip is the most common bug: it produces the same triplet once per copy ofa[i]. - Overflow.
nums[i] + nums[j] + nums[k]can exceedint; every method sums inlong. The test with three10^9values and one-2 × 10^9exercises this. - Early exit.
if (a[i] > 0) break;is a safe optimisation only because the array is sorted and the target is zero. For a general target it must be removed. - Output order. The two-pointer method returns triplets in lexicographic order automatically; the hash-based methods do not, which is why the tests normalise with
sorted.
Related problems
- Two Sum is the inner step of approaches 2 and 3.
- 4 Sum adds one more fixed element and a general target.
- Largest Subarray with Sum 0 uses the “have I seen the complement” idea on prefix sums.