Three Sum and Avoiding Duplicates
24 min · Intermediate
Need help? Ask a tutor on WhatsApp (opens in a new tab)Three sum is the classic next step after pair sum. A triple search sounds like three loops, but sorting plus one loop and two pointers brings it from O(n cubed) to O(n squared).
Sort, fix one value, then two pointers
Three sum asks for all unique triples in a list that add up to a target, usually zero. Sort the list, fix one value, and search the pair on the right with two pointers in O(n squared) time.
Find three values that add up to zero. Sort the list, fix the first value, and the rest is a pair-sum problem on what remains to the right. That turns three loops into one loop plus two pointers.
Example 1 · Brute force with three loops
nums = [-1, 0, 1, 2, -1, -4]found = set()for i in range(len(nums)): for j in range(i + 1, len(nums)): for k in range(j + 1, len(nums)): if nums[i] + nums[j] + nums[k] == 0: found.add(tuple(sorted((nums[i], nums[j], nums[k]))))print(sorted(found))[(-1, -1, 2), (-1, 0, 1)]
What this code does
- Three nested loops try every triple, so the time is O(n cubed). A set of sorted tuples removes repeats.
- It is fine for six values and far too slow for thousands.
Example 2 · Sort, fix one value, then two pointers
def three_sum_set(nums): nums = sorted(nums) found = set() for i in range(len(nums) - 2): left, right = i + 1, len(nums) - 1 while left < right: total = nums[i] + nums[left] + nums[right] if total < 0: left += 1 elif total > 0: right -= 1 else: found.add((nums[i], nums[left], nums[right])) left += 1 right -= 1 return sorted(found)
print(three_sum_set([-1, 0, 1, 2, -1, -4]))[(-1, -1, 2), (-1, 0, 1)]
What this code does
- A set of tuples removes repeated triples, but it uses extra space and hides the real work.
- The next section avoids repeats by skipping equal values.
Skip duplicates
Equal values next to each other give the same triple again. Skip the next outer value if it equals the one before, and after a match move both inner pointers past equal values.
Example 3 · The pair search inside
nums = sorted([-1, 0, 1, 2, -1, -4])i = 0left, right = i + 1, len(nums) - 1while left < right: total = nums[i] + nums[left] + nums[right] print(nums[i], nums[left], nums[right], total) if total < 0: left += 1 elif total > 0: right -= 1 else: break-4 -1 2 -3 -4 -1 2 -3 -4 0 2 -2 -4 1 2 -1
What this code does
- With the first value fixed at -4, the pair search can never reach zero.
- Each line is one round of the inner loop.
Example 4 · Unique triples
def three_sum(nums): nums = sorted(nums) result = [] for i in range(len(nums) - 2): if i > 0 and nums[i] == nums[i - 1]: continue left, right = i + 1, len(nums) - 1 while left < right: total = nums[i] + nums[left] + nums[right] if total < 0: left += 1 elif total > 0: right -= 1 else: result.append([nums[i], nums[left], nums[right]]) left += 1 right -= 1 while left < right and nums[left] == nums[left - 1]: left += 1 while left < right and nums[right] == nums[right + 1]: right -= 1 return result
print(three_sum([-1, 0, 1, 2, -1, -4]))print(three_sum([0, 0, 0, 0]))[[-1, -1, 2], [-1, 0, 1]] [[0, 0, 0]]
What this code does
- The first call returns two triples, each once, even though -1 appears twice.
- The second call shows that four zeros give one triple, not four.
- Sorting makes equal values neighbours, so
nums[i] == nums[i - 1]is enough to spot repeats. - Results come out in sorted order.
Stop early and count the cost
Once the fixed value is above zero, every value to its right is also above zero, so no triple can sum to zero. The loop can stop there.
Example 5 · Duplicates in the middle
def three_sum(nums): nums = sorted(nums) result = [] for i in range(len(nums) - 2): if i > 0 and nums[i] == nums[i - 1]: continue left, right = i + 1, len(nums) - 1 while left < right: total = nums[i] + nums[left] + nums[right] if total < 0: left += 1 elif total > 0: right -= 1 else: result.append([nums[i], nums[left], nums[right]]) left += 1 right -= 1 while left < right and nums[left] == nums[left - 1]: left += 1 while left < right and nums[right] == nums[right + 1]: right -= 1 return result
print(three_sum([-2, 0, 0, 2, 2]))print(three_sum([1, 2, 3]))[[-2, 0, 2]] []
What this code does
- Two zeros and two 2s still give a single triple, -2 + 0 + 2.
- No triple sums to zero in the second call, so the result is an empty list.
Example 6 · Stop early and count the cost
def three_sum_fast(nums): nums = sorted(nums) result = [] for i in range(len(nums) - 2): if nums[i] > 0: break if i > 0 and nums[i] == nums[i - 1]: continue left, right = i + 1, len(nums) - 1 while left < right: total = nums[i] + nums[left] + nums[right] if total < 0: left += 1 elif total > 0: right -= 1 else: result.append([nums[i], nums[left], nums[right]]) left += 1 right -= 1 while left < right and nums[left] == nums[left - 1]: left += 1 return result
print(three_sum_fast([3, -2, 1, 0, -1, 2, -3]))[[-3, 0, 3], [-3, 1, 2], [-2, -1, 3], [-2, 0, 2], [-1, 0, 1]]
What this code does
- Sorting costs O(n log n) and the main loop costs O(n squared), so the total is O(n squared).
- Extra space is O(1) besides the result.
Any target, not only zero
For a target other than zero, compare the three-value total with the target instead of zero. The rest of the code is the same, so this one function covers many problems.
Example 7 · Three sum closest
def closest_sum(nums, target): nums = sorted(nums) best = nums[0] + nums[1] + nums[2] for i in range(len(nums) - 2): left, right = i + 1, len(nums) - 1 while left < right: total = nums[i] + nums[left] + nums[right] if abs(total - target) < abs(best - target): best = total if total < target: left += 1 elif total > target: right -= 1 else: return total return best
print(closest_sum([-1, 2, 1, -4], 1))2
What this code does
- Same fix-one-then-search structure, but the goal is the smallest distance to the target.
- An exact hit returns at once. Time is O(n squared).
Example 8 · Any target, not only zero
def triples_with_sum(nums, target): nums = sorted(nums) result = [] for i in range(len(nums) - 2): if i > 0 and nums[i] == nums[i - 1]: continue left, right = i + 1, len(nums) - 1 while left < right: total = nums[i] + nums[left] + nums[right] if total < target: left += 1 elif total > target: right -= 1 else: result.append([nums[i], nums[left], nums[right]]) left += 1 right -= 1 while left < right and nums[left] == nums[left - 1]: left += 1 return result
print(triples_with_sum([5, 1, 3, 2, 4, 6], 10))[[1, 3, 6], [1, 4, 5], [2, 3, 5]]
What this code does
- Replace the 0 in the comparisons with
target. - The skipping rules keep the answers unique.
Next step
Check your understanding
11 quick questions