LeetCode Problem: 15 — 3Sum
Difficulty: Medium
Topics: Array, Sorting, Two Pointers
Pattern: Sorting + Two Pointers
Series: LeetCode Interview Preparation — From Beginner to Expert
Some LeetCode problems teach you a data structure.
Some teach you an algorithm.
And some teach you how to combine patterns.
3Sum is one of those problems.
If you've already understood Two Sum and Two Sum II, this problem is the perfect next step.
The key idea is:
3Sum
=
Fix one number
+
Solve Two Sum using Two PointersBut there is another challenge:
How do we avoid duplicate answers?
That makes 3Sum an extremely valuable interview problem.
Given an integer array:
numsreturn all unique triplets:
[a, b, c]such that:
a + b + c = 0The solution must not contain duplicate triplets.
Input:
[−1, 0, 1, 2, −1, −4]Output:
[
[-1, -1, 2],
[-1, 0, 1]
]Notice that:
[-1, 0, 1]and:
[0, -1, 1]represent the same triplet.
We only return one of them.
Input:
[0, 1, 1]There is no triplet whose sum is zero.
Output:
[]Input:
[0, 0, 0]The answer is:
[[0, 0, 0]]Not:
[
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
]Duplicate handling is a major part of this problem.
The most obvious solution is to choose every possible combination of three numbers.
We could use three loops:
for i in range(n):
for j in range(i + 1, n):
for k in range(j + 1, n):
if nums[i] + nums[j] + nums[k] == 0:
...This checks every possible triplet.
The number of combinations is approximately:
n³Therefore:
Time = O(n³)That's too slow for large arrays.
But brute force is still useful.
Why?
Because during an interview, you should first establish the straightforward solution before optimizing it.
Yes.
Think back to Two Sum II.
We learned that a sorted array allows us to use:
Two PointersSo what if we sort the array?
For example:
[-1, 0, 1, 2, -1, -4]becomes:
[-4, -1, -1, 0, 1, 2]Now we can exploit the ordering.
Pick one number.
Let's call it:
nums[i]Then our problem becomes:
nums[i] + nums[left] + nums[right] = 0Rearrange:
nums[left] + nums[right] = -nums[i]That's simply a Two Sum problem.
So:
3Sum
↓
Fix one number
↓
Find two numbers
↓
Use Two PointersThis is the key insight.
Suppose:
nums = [-4, -1, -1, 0, 1, 2]Fix:
i = 0
nums[i] = -4Now we need:
left + right = 4Pointers:
i
↓
[-4, -1, -1, 0, 1, 2]
↑ ↑
left rightCalculate:
-1 + 2 = 1Too small.
Move:
left++Now:
-1 + 2 = 1Still too small.
Move again:
0 + 2 = 2Still too small.
Move again:
1 + 2 = 3Still too small.
No solution for -4.
Then move to the next fixed number.
Now:
i = 1
nums[i] = -1We need:
left + right = 1Start:
[-4, -1, -1, 0, 1, 2]
↑ ↑ ↑
i left rightCalculate:
-1 + 2 = 1Therefore:
-1 + -1 + 2 = 0Found:
[-1, -1, 2]Continue searching.
Move both pointers:
left++
right--Now:
0 + 1 = 1Therefore:
-1 + 0 + 1 = 0Found:
[-1, 0, 1]The complete strategy is:
Sort the array.
nums.sort()Loop through every possible first element.
for i in range(len(nums)):Skip duplicate values for i.
if i > 0 and nums[i] == nums[i - 1]:
continueCreate two pointers:
left = i + 1
right = len(nums) - 1Calculate:
total = nums[i] + nums[left] + nums[right]If:
total < 0move:
left += 1If:
total > 0move:
right -= 1If:
total == 0store the triplet and move both pointers.
def threeSum(nums):
nums.sort()
result = []
n = len(nums)
for i in range(n - 2):
# Skip duplicate first values
if i > 0 and nums[i] == nums[i - 1]:
continue
left = i + 1
right = n - 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
# Skip duplicate left values
while left < right and nums[left] == nums[left - 1]:
left += 1
# Skip duplicate right values
while left < right and nums[right] == nums[right + 1]:
right -= 1
return resultLet's take:
nums = [-1, 0, 1, 2, -1, -4]First sort:
[-4, -1, -1, 0, 1, 2]i = 0
nums[i] = -4Pointers:
left = 1
right = 5Values:
-4 + -1 + 2 = -3Too small.
Move left:
left = 2Now:
-4 + -1 + 2 = -3Still too small.
Continue.
Eventually:
left = 4
right = 5Then:
-4 + 1 + 2 = -1Still no solution.
i = 1
nums[i] = -1Pointers:
left = 2
right = 5Calculate:
-1 + -1 + 2 = 0Found:
[-1, -1, 2]Move both:
left++
right--Now:
left = 3
right = 4Calculate:
-1 + 0 + 1 = 0Found:
[-1, 0, 1]Now:
i = 2
nums[i] = -1But:
nums[2] == nums[1]So we skip it.
Why?
Because we already considered all combinations starting with -1.
If we process the second -1 again, we would generate duplicate triplets.
This is one of the most important parts of the problem.
Suppose the sorted array is:
[-2, -2, 0, 0, 2, 2]There may be many ways to select the same values.
But the output should contain each unique triplet only once.
There are two places where duplicates matter.
We use:
if i > 0 and nums[i] == nums[i - 1]:
continueThis prevents:
i = 1from repeating the work already done at:
i = 0when both values are equal.
Suppose we find:
[-1, 0, 1]and there are multiple zeros or ones afterward.
We need to skip repeated values.
That's why we use:
while left < right and nums[left] == nums[left - 1]:
left += 1and:
while left < right and nums[right] == nums[right + 1]:
right -= 1Suppose:
total == 0We've found a valid combination.
Could we simply move left?
Yes, but we'd need to reason carefully about the next possibilities.
The clean approach is:
left += 1
right -= 1We've already used both values in the current valid combination.
Then we skip duplicates.
This keeps the algorithm clean and efficient.
Sorting gives us two major advantages.
We can determine which pointer to move based on whether the sum is too small or too large.
Duplicates become adjacent:
[-4, -1, -1, 0, 1, 2]Now they're easy to skip.
Without sorting, duplicate handling becomes much more complicated.
Sorting costs:
O(n log n)The outer loop runs:
O(n)For each fixed element, the two pointers together scan at most:
O(n)Therefore:
Total = O(n²)So:
O(n²)Ignoring the output:
O(1)depending on the sorting implementation.
In Python, sort() uses additional implementation-dependent memory, but algorithmically the two-pointer portion uses constant extra space.
The output itself can contain many triplets, so output storage is separate from auxiliary space.
The brute-force solution does:
i
j
kwhich gives:
O(n³)Our optimized solution does:
i
↓
left → ← rightFor each i, the two pointers scan the remaining array in linear time.
Therefore:
O(n × n)
=
O(n²)That's a huge improvement.
The most important thing to remember isn't the exact code.
Remember this transformation:
3Sum
↓
Sort
↓
Fix one element
↓
Two Sum
↓
Two PointersThis is a reusable strategy.
When you see:
Find three numbers satisfying a condition.
Ask:
Can I fix one number and turn the remaining problem into Two Sum?
That question can unlock many problems.
Because the array is sorted, we can sometimes stop early.
Suppose:
nums[i] > 0Remember that the array is sorted.
If the first number is already positive, then every number after it is also positive.
Therefore:
positive + positive + positive > 0It can never equal zero.
So we can safely:
if nums[i] > 0:
breakAn optimized version becomes:
def threeSum(nums):
nums.sort()
result = []
for i in range(len(nums) - 2):
if nums[i] > 0:
break
if i > 0 and nums[i] == nums[i - 1]:
continue
left = i + 1
right = 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 resultThe asymptotic complexity remains:
O(n²)but this can reduce unnecessary work.
Without sorting, the two-pointer logic doesn't work correctly.
Always start with:
nums.sort()iThis:
for i in range(n):isn't enough.
You need:
if i > 0 and nums[i] == nums[i - 1]:
continueOtherwise duplicate triplets can appear.
Be careful with pointer movement.
A simple and safe order is:
Find valid triplet
↓
Add it
↓
Move left/right
↓
Skip duplicatesDon't accidentally skip a valid combination.
The problem asks for all unique triplets.
So finding:
[-1, -1, 2]doesn't mean you're finished.
You must continue searching.
Unlike Two Sum II, the original input here does not need to be sorted.
We sort it ourselves:
nums.sort()That's part of the algorithm.
A Hash Set can also be used to construct solutions, but if you are asked about optimal time and duplicate handling, the sorted + two-pointer approach is usually the cleanest standard solution.
More importantly, you should understand why it works.
Once you've solved 3Sum, interviewers can easily extend it.
Instead of finding:
sum == 0find the triplet whose sum is closest to a target.
This is:
LeetCode #16 — 3Sum Closest
The same sorting + two-pointer pattern applies.
Find four numbers whose sum equals a target.
The pattern becomes:
Fix first
+
Fix second
+
Two PointersThis leads to:
LeetCode #18 — 4Sum
Instead of:
a + b + c = 0find:
a + b + c = targetThe algorithm remains almost identical.
Instead of returning the triplets, count how many valid combinations exist.
Now you need to carefully handle duplicates and indices.
When you encounter a problem containing:
Think:
Sort
↓
Fix one
↓
Two PointersThis should become an interview reflex.
This is where your LeetCode pattern library starts becoming powerful.
Hash MapSorted Array
+
Two PointersSort
+
Fix One
+
Two PointersSort
+
Fix Two
+
Two PointersYou can see the progression.
We're not learning completely unrelated problems.
We're building on previous patterns.
If the interviewer asks:
"Explain your approach."
A strong answer would be:
"I'll first sort the array. Then I'll iterate through each element as the first element of the triplet. For every fixed element, I'll use two pointers on the remaining sorted portion to find two values whose sum is the negative of the fixed value. If the total is too small, I'll move the left pointer forward; if it's too large, I'll move the right pointer backward. After finding a valid triplet, I'll move both pointers and skip duplicates. Sorting takes O(n log n), and the nested iteration with two pointers takes O(n²), so the overall complexity is O(n²)."
That's the kind of explanation interviewers want to hear.
Problem:
Find all unique triplets
whose sum equals zero.
First:
Sort the array.
For each i:
Skip duplicate nums[i]
left = i + 1
right = n - 1
while left < right:
total =
nums[i] +
nums[left] +
nums[right]
if total < 0:
left++
elif total > 0:
right--
else:
save triplet
left++
right--
skip duplicates
Optimization:
If nums[i] > 0:
break
Complexity:
Time: O(n²)
Extra Space: O(1)
excluding outputDon't stop at the LeetCode solution.
Try these variations.
Solve 3Sum with a custom target instead of zero.
Find the triplet whose sum is closest to a target.
Find all triplets whose sum equals 10.
Find the number of unique triplets whose sum equals zero.
Solve 4Sum.
Given an array and target T, determine whether any three numbers add up to T.
Given an array, find three numbers with the maximum possible sum below a target.
These variations will help you understand the pattern rather than memorize one solution.
There are actually four lessons hidden inside this one problem.
Sorting can unlock algorithms.
A difficult problem can sometimes be reduced to a simpler problem you've already solved.
3Sum → Two SumTwo Pointers can reduce an O(n³) brute-force approach to O(n²).
Duplicate handling is part of algorithm design—not an afterthought.
3Sum is one of the most important stepping stones in your LeetCode journey.
The solution isn't about memorizing this:
left += 1
right -= 1The real skill is recognizing the structure:
Three numbers
↓
Fix one
↓
Remaining problem becomes Two Sum
↓
Sorted array
↓
Two Pointers
↓
O(n²)And there's an even bigger lesson.
When an interview problem looks complicated, don't immediately search for a completely new algorithm.
Ask:
"Can I reduce this problem to something I already know?"
That's exactly what we did here.
We took:
3Sumand reduced it to:
Two Sum + Two PointersThat way of thinking is one of the most valuable skills you can develop for software engineering interviews.
Pixels to Perfection Design that Impresses