3Sum
You get a list of integers nums. Find every triplet [a, b, c] of values taken from three different positions of nums with a + b + c = 0. Write each triplet in non-decreasing order (a ≤ b ≤ c) and list each distinct triplet once, even when several choices of positions produce it. Return the triplets sorted by their first value, then by their second.
Function
- numsinteger-array
- the list of integers, with at least three elements
- Returnsinteger-2d-array
- every distinct triplet that adds up to 0, each in non-decreasing order, the list sorted
Constraints
3 ≤ nums.length ≤ 3000-105 ≤ nums[i] ≤ 105- At least one triplet adds up to 0.
- Two triplets are the same when they hold the same three values.
Examples
- Input
- nums = [-2, 0, 1, 1, -1, 2]
- Output
- [[-2, 0, 2], [-2, 1, 1], [-1, 0, 1]]
- Explanation
- -2 + 0 + 2, -2 + 1 + 1 and -1 + 0 + 1 all make 0.
[-2, 1, 1]may use the value 1 twice because 1 sits at two positions, while[-1, 0, 1]can be built with either 1 but appears once.
- Input
- nums = [0, 0, 0, 0]
- Output
- [[0, 0, 0]]
- Explanation
- Any three of the four zeros add up to 0. That is four choices of positions, but they all give the same triplet, so the answer holds
[0, 0, 0]once.
+15 hidden tests on Submit
Follow-up
The same pattern solves 4Sum: fix two values and run two pointers on the rest. Can you write it in O(n³) and keep the duplicate rules right at every level?
Hints
Open them one at a time. Each one gives away a little more.
Sort the list first. A sorted list helps twice: each triplet comes out in order, and equal values sit next to each other, so a repeat always sits right after the value it repeats.
Fix the smallest value of the triplet,
nums[i]. The other two must add up to-nums[i], and they come from the sorted values to the right ofi. That is a pair-sum question on a sorted list.For that pair, start one pointer right after
iand one at the last index. If the three values add up to less than 0, move the left pointer right; if more, move the right pointer left. After a match, move both and step the left pointer past copies of its value. Skip anyiwhose value equals the one before it.
Solution
Two things make 3Sum harder than it looks. Checking every triple costs O(n³), and the answer must hold each triplet once even when values repeat. Sorting fixes both: equal values end up side by side, so you skip repeats by comparing neighbours, and once the smallest value is fixed, the other two form a pair-sum problem on a sorted list that two pointers solve in one sweep.
Try every triple
Correct, but does not finish on the largest tests
Intuition
Sort the list first. Then any three positions i < j < k give values that are already in order, nums[i] ≤ nums[j] ≤ nums[k], so a triplet is written correctly the moment you find it. Three nested loops visit every choice of positions, so no triplet can be missed.
Repeats come next. The first example sorted is [-2, -1, 0, 1, 1, 2], and [-1, 0, 1] can take its 1 from index 3 or index 4. Each loop therefore skips a position whose value equals the one that same loop tried before it. Every loop then tries each distinct value once, and every distinct triplet comes out once, already in sorted order. The skip only compares with the previous position inside the same loop, so [-2, 1, 1] still uses both ones.
The cost is the problem. There are about n³/6 triples: for 3000 numbers that is 4.5 × 10^9 sums, far beyond any time limit.
Algorithm
- Sort
nums. - Loop
iover the positions and skipiwhennums[i]equalsnums[i-1]. - Inside it, loop
jfromi+1and skipjwhenj > i+1andnums[j]equalsnums[j-1]. - Inside that, loop
kfromj+1with the same skip rule, and record[nums[i], nums[j], nums[k]]when the three add up to 0. - Return the triplets in the order you found them. They are already sorted.
def threeSum(nums):
nums = sorted(nums)
n = len(nums)
triplets = []
for i in range(n - 2):
if i > 0 and nums[i] == nums[i - 1]:
continue # same first value as the round before
for j in range(i + 1, n - 1):
if j > i + 1 and nums[j] == nums[j - 1]:
continue # same second value as the round before
for k in range(j + 1, n):
if k > j + 1 and nums[k] == nums[k - 1]:
continue # same third value as the round before
if nums[i] + nums[j] + nums[k] == 0:
triplets.append([nums[i], nums[j], nums[k]])
return tripletsFix one value, find the pair with a hash set
Intuition
Once the first value nums[i] is fixed, you need two later values that add up to -nums[i]. That is Two Sum. Walk j to the right of i and keep a set of the values you have passed. At each j the missing value is need = -nums[i] - nums[j]. If need is in the set, [nums[i], need, nums[j]] adds up to 0. A set lookup costs O(1) on average, so one i costs O(n) and the whole search O(n²).
Sorting still does the bookkeeping. Skip an i whose value equals the one before it. After a match, move j past every copy of nums[j]: with the first and third values fixed, the middle one is fixed too, so another copy could only repeat the same triplet. Since need comes from an earlier position of the sorted list, need ≤ nums[j] and the triplet is in order. You can also stop as soon as nums[i] > 0: the two values after it are at least as large, so the sum cannot reach 0.
One detail: as j moves right, nums[j] grows and need shrinks, so the triplets for one i come out with the middle value going down. In [-2, -1, 0, 1, 1, 2] with i = 0, you find [-2, 1, 1] at the second 1 and then [-2, 0, 2] at the 2. Reverse each group before you add it to the answer. The C and R versions mark seen values in an array indexed by value instead of a hash set, which works because every value lies within ±10^5.
Algorithm
- Sort
nums. - For each
i, stop whennums[i] > 0and skipiwhennums[i]equalsnums[i-1]. - Start an empty set. For each
jfromi+1, computeneed = -nums[i] - nums[j]. Ifneedis in the set, record[nums[i], need, nums[j]]and movejpast the copies ofnums[j]. - Add
nums[j]to the set and go on to the nextj. - Reverse the triplets found for this
iand append them to the answer.
def threeSum(nums):
nums = sorted(nums)
n = len(nums)
triplets = []
for i in range(n - 2):
if nums[i] > 0:
break # the two values after it are at least as large
if i > 0 and nums[i] == nums[i - 1]:
continue # this first value was already handled
group = []
seen = set() # values between position i and position j
j = i + 1
while j < n:
need = -nums[i] - nums[j]
if need in seen:
group.append([nums[i], need, nums[j]])
while j + 1 < n and nums[j + 1] == nums[j]:
j += 1
seen.add(nums[j])
j += 1
# need shrinks as nums[j] grows, so this group came out backwards
group.reverse()
triplets.extend(group)
return tripletsSort and use two pointers
Intuition
The sorted order can replace the set. Fix nums[i], put lo at i+1 and hi at the last index, and look at nums[i] + nums[lo] + nums[hi]. Below 0, you need a larger value, so lo moves right. Above 0, you need a smaller one, so hi moves left. At exactly 0, record the triplet and move both.
No triplet is lost. When the sum is below 0, nums[lo] falls short even with the largest value left, nums[hi], so it cannot pair with anything still in range and dropping it loses nothing. Above 0 is the mirror case: nums[hi] is too large even with the smallest value left. Every step drops one value for good, so one i costs at most n steps and the whole search O(n²), with no memory beyond the sort and the output.
Take the sorted [-2, -1, 0, 1, 1, 2]. With i = 0 (value -2), lo starts at -1 and hi at 2: the sum is -1, so lo moves to 0. Now -2 + 0 + 2 = 0, so you record [-2, 0, 2] and both pointers land on the two 1s, which give [-2, 1, 1]. With i = 1 (value -1), 0 and 2 give 1, so hi moves to the second 1, and -1 + 0 + 1 = 0 records [-1, 0, 1]. The value 0 at i = 2 finds nothing, and at i = 3 the value is positive, so the search stops.
Repeats need two rules. Skip an i whose value equals the one before it. After a match, step lo past copies of the value it used. hi needs no rule of its own: with lo on a larger value, a copy of the old nums[hi] now gives a sum above 0 and moves away by itself. Because i visits the distinct values in increasing order and lo only moves right, the triplets come out sorted.
Algorithm
- Sort
nums. - For each
i, stop whennums[i] > 0and skipiwhennums[i]equalsnums[i-1]. - Set
lo = i+1andhi = n-1. Whilelo < hi, add upnums[i],nums[lo]andnums[hi]. - If the sum is below 0, move
loright. If it is above 0, movehileft. - If it is 0, record the triplet, move both pointers, then move
lopast copies of the value it used. - Return the triplets. They are already sorted.
def threeSum(nums):
nums = sorted(nums)
n = len(nums)
triplets = []
for i in range(n - 2):
if nums[i] > 0:
break # the two values after it are at least as large
if i > 0 and nums[i] == nums[i - 1]:
continue # this first value was already handled
lo, hi = i + 1, n - 1
while lo < hi:
total = nums[i] + nums[lo] + nums[hi]
if total < 0:
lo += 1
elif total > 0:
hi -= 1
else:
triplets.append([nums[i], nums[lo], nums[hi]])
lo += 1
hi -= 1
while lo < hi and nums[lo] == nums[lo - 1]:
lo += 1
return triplets
Pitfalls and edge cases
Most wrong answers come from repeated values, so test with inputs that have them.
- Skipping
iwhennums[i]equalsnums[i+1]keeps the last copy of each value as the first element, and the copies before it are gone. In[-1, -1, 2]that loses[-1, -1, 2]. Compare with the previous position,nums[i-1]. - Stopping when
nums[i] ≥ 0instead ofnums[i] > 0misses[0, 0, 0]. - Removing repeats at the end instead of skipping them. On 3000 zeros the two-pointer loop records millions of copies of
[0, 0, 0]before any cleanup, and in several languages a set of lists compares the lists by identity, so the copies survive anyway. - Using one position twice. A hash-set version that fills the set with the whole list up front turns
[-2, 1, 3]into[-2, 1, 1]by using the single 1 twice. Only look up values at positions you have already passed. - Returning the triplets out of order. The comparison is exact, so the hash-set version must reverse each group, and a solution that collects triplets in a set must sort them at the end.
Frequently asked questions4
What is the time complexity of 3Sum?
The sort-and-two-pointers solution runs in O(n²) time. Sorting costs O(n log n), and each of the n choices of the first value takes one O(n) sweep. It needs O(1) extra space apart from the sort and the output. Checking every triple takes O(n³) instead.
How does 3Sum avoid duplicate triplets?
It sorts the list, so equal values sit next to each other. Then it skips a first value that equals the one before it, and after each match it moves the left pointer past copies of the value it used. Every triplet is found once, from the first copies of its values, so no set of results is needed.
Should I use two pointers or a hash set for 3Sum?
Both run in O(n²) time. Two pointers need no extra memory, and the sorted order hands you the triplets already in order. A hash set costs O(n) memory and needs care to keep positions distinct and the output sorted. The hash-set idea matters when you cannot sort, as in Two Sum where you return the original indices.
Can 3Sum be solved faster than O(n²)?
Not by much. The best known algorithms beat n² only by a few logarithmic factors, and many hardness results in computational geometry assume that no algorithm reaches a power of n below 2. Those faster algorithms are research results, so O(n²) is the answer interviews expect.
Similar problems
Problems that use the same ideas. Solving two or three of them is what makes a pattern stick.
Python
def threeSum(nums):
# Write code hereCase 1
Case 2
Input
nums = [-2, 0, 1, 1, -1, 2]
Expected
[[-2, 0, 2], [-2, 1, 1], [-1, 0, 1]]