15. 3Sum - Explanation
Description
Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] where nums[i] + nums[j] + nums[k] == 0, and the indices i, j and k are all distinct.
The output should not contain any duplicate triplets. You may return the output and the triplets in any order.
Example 1:
Input: nums = [-1,0,1,2,-1,-4]
Output: [[-1,-1,2],[-1,0,1]]Explanation:nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0.nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0.nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0.
The distinct triplets are [-1,0,1] and [-1,-1,2].
Example 2:
Input: nums = [0,1,1]
Output: []Explanation: The only possible triplet does not sum up to 0.
Example 3:
Input: nums = [0,0,0]
Output: [[0,0,0]]Explanation: The only possible triplet sums up to 0.
Constraints:
3 <= nums.length <= 1000-10^5 <= nums[i] <= 10^5
Topics
Recommended Time & Space Complexity
You should aim for a solution with O(n^2) time and O(1) space, where n is the size of the input array.
Hint 1
A brute force solution would be to check for every triplet in the array. This would be an O(n^3) solution. Can you think of a better way?
Hint 2
Can you think of an algorithm after sorting the input array? What can we observe by rearranging the given equation in the problem?
Hint 3
We can iterate through nums with index i and get nums[i] = -(nums[j] + nums[k]) after rearranging the equation, making -nums[i] = nums[j] + nums[k]. For each index i, we should efficiently calculate the j and k pairs without duplicates. Which algorithm is suitable to find j and k pairs?
Hint 4
To efficiently find the j and k pairs, we run the two pointer approach on the elements to the right of index i as the array is sorted. When we run two pointer algorithm, consider j and k as pointers (j is at left, k is at right) and target = -nums[i], if the current sum num[j] + nums[k] < target then we need to increase the value of current sum by incrementing j pointer. Else if the current sum num[j] + nums[k] > target then we should decrease the value of current sum by decrementing k pointer. How do you deal with duplicates?
Hint 5
When the current sum nums[j] + nums[k] == target add this pair to the result. We can move j or k pointer until j < k and the pairs are repeated. This ensures that no duplicate pairs are added to the result.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Sorting - Sorting the array enables efficient duplicate handling and two-pointer technique
- Two pointers - The optimal solution uses two pointers to find pairs that sum to a target
- Hash maps - An alternative approach uses hash maps for O(1) lookups
- Handling duplicates - Skipping duplicate values to avoid repeated triplets in the result
1. Brute Force
Intuition
The brute-force approach simply tries every possible triplet.
Since we check all combinations (i, j, k) with i < j < k, we are guaranteed to find all sets of three numbers that sum to zero.
Sorting helps keep the triplets in order and makes it easier to avoid duplicates by storing them in a set.
Algorithm
- Sort the array to make handling duplicates easier.
- Create an empty set
resto store unique triplets. - Use three nested loops:
- For each
i, - For each
j > i, - For each
k > j,- Check if
nums[i] + nums[j] + nums[k] == 0. - If true, add the sorted triplet to the set.
- Check if
- For each
- Convert the set of tuples back into a list of lists.
- Return the result.
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
res = set()
nums.sort()
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:
tmp = [nums[i], nums[j], nums[k]]
res.add(tuple(tmp))
return [list(i) for i in res]Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of triplets and is the length of the given array.
2. Hash Map
Intuition
After sorting the array, we can fix two numbers and look for the third number that completes the triplet.
To do this efficiently, we use a hash map that stores how many times each number appears.
As we pick the first and second numbers, we temporarily reduce their counts in the map so we don't reuse them.
Then we check whether the needed third value still exists in the map.
Sorting also helps us skip duplicates easily so we only add unique triplets.
Algorithm
- Sort the array to organize duplicates and allow easy skipping.
- Build a frequency map
countfor all numbers. - Initialize an empty list
resfor storing valid triplets. - Loop through each index
i:- Decrease the count of
nums[i](so it won't be reused). - Skip duplicates of the first element.
- Loop through each index
j > i:- Decrease the count of
nums[j]. - Skip duplicates of the second element.
- Compute the needed third value:
target = -(nums[i] + nums[j]) - If
targetstill has a positive count, add the triplet.
- Decrease the count of
- After finishing all
js, restore the counts for the second loop by adding back the decremented values.
- Decrease the count of
- Return
rescontaining all found triplets.
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
count = defaultdict(int)
for num in nums:
count[num] += 1
res = []
for i in range(len(nums)):
count[nums[i]] -= 1
if i and nums[i] == nums[i - 1]:
continue
for j in range(i + 1, len(nums)):
count[nums[j]] -= 1
if j - 1 > i and nums[j] == nums[j - 1]:
continue
target = -(nums[i] + nums[j])
if count[target] > 0:
res.append([nums[i], nums[j], target])
for j in range(i + 1, len(nums)):
count[nums[j]] += 1
return resTime & Space Complexity
- Time complexity:
- Space complexity:
3. Two Pointers
Intuition
After sorting the array, we can fix one number and then search for the other two using the two-pointer technique.
Sorting helps in two ways:
- It lets us skip duplicates easily.
- It ensures that moving the left or right pointer will increase or decrease the sum in a predictable way.
For each fixed number a, we place two pointers:
lstarts just afteri,rstarts at the end.
If the current sum is too large, we move r left to reduce it.
If the sum is too small, we move l right to increase it.
When the sum is exactly zero, we record the triplet and skip duplicates.
Algorithm
- Sort the array to handle duplicates and enable two-pointer logic.
- Loop through the array using index
i:- Let
a = nums[i]. - If
a > 0, break (all remaining numbers are positive). - Skip duplicate values for the first number.
- Let
- Set two pointers:
l = i + 1r = len(nums) - 1
- While
l < r:- Compute
threeSum = a + nums[l] + nums[r]. - If
threeSum > 0, moverleft. - If
threeSum < 0, movelright. - If
threeSum == 0:- Add the triplet to the result.
- Move both pointers inward.
- Skip duplicates at the left pointer.
- Compute
- Return the list of all valid triplets.
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
res = []
nums.sort()
for i, a in enumerate(nums):
if a > 0:
break
if i > 0 and a == nums[i - 1]:
continue
l, r = i + 1, len(nums) - 1
while l < r:
threeSum = a + nums[l] + nums[r]
if threeSum > 0:
r -= 1
elif threeSum < 0:
l += 1
else:
res.append([a, nums[l], nums[r]])
l += 1
r -= 1
while nums[l] == nums[l - 1] and l < r:
l += 1
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- or extra space depending on the sorting algorithm.
- space for the output list.
Where is the number of triplets and is the length of the given array.
Common Pitfalls
Forgetting to Skip Duplicates
A common mistake is not properly skipping duplicate values, which leads to duplicate triplets in the result. After sorting, when you find a valid triplet, you must skip over all identical values for the first element (outer loop) and the left pointer. Failing to do this causes wrong answers on inputs like [-1, -1, 0, 1, 1].
Not Sorting the Array First
The two-pointer approach only works correctly on a sorted array. Without sorting, moving pointers based on sum comparisons does not guarantee you will find all valid triplets. Always sort the input array before applying the two-pointer technique.
Incorrect Early Termination
When the first element nums[i] is positive, all remaining elements are also positive (since the array is sorted), so no triplet can sum to zero. However, incorrectly breaking when nums[i] >= 0 instead of nums[i] > 0 misses cases like [0, 0, 0]. The break condition should be nums[i] > 0, not nums[i] >= 0.
Sign in to join the discussion