194 lines
8.0 KiB
YAML
194 lines
8.0 KiB
YAML
title: 3Sum
|
|
slug: three-sum
|
|
difficulty: medium
|
|
leetcode_id: 15
|
|
leetcode_url: https://leetcode.com/problems/3sum/
|
|
categories:
|
|
- arrays
|
|
- two-pointers
|
|
- sorting
|
|
patterns:
|
|
- two-pointers
|
|
|
|
function_signature: "def three_sum(nums: list[int]) -> list[list[int]]:"
|
|
|
|
test_cases:
|
|
visible:
|
|
- input: { nums: [-1, 0, 1, 2, -1, -4] }
|
|
expected: [[-1, -1, 2], [-1, 0, 1]]
|
|
- input: { nums: [0, 1, 1] }
|
|
expected: []
|
|
- input: { nums: [0, 0, 0] }
|
|
expected: [[0, 0, 0]]
|
|
hidden:
|
|
- input: { nums: [-2, 0, 1, 1, 2] }
|
|
expected: [[-2, 0, 2], [-2, 1, 1]]
|
|
- input: { nums: [1, 2, -2, -1] }
|
|
expected: []
|
|
- input: { nums: [-4, -2, -2, -2, 0, 1, 2, 2, 2, 3, 3, 4, 4, 6, 6] }
|
|
expected: [[-4, -2, 6], [-4, 0, 4], [-4, 1, 3], [-4, 2, 2], [-2, -2, 4], [-2, 0, 2]]
|
|
- input: { nums: [0, 0, 0, 0] }
|
|
expected: [[0, 0, 0]]
|
|
- input: { nums: [-1, -1, -1, 2] }
|
|
expected: [[-1, -1, 2]]
|
|
- input: { nums: [3, 0, -2, -1, 1, 2] }
|
|
expected: [[-2, -1, 3], [-2, 0, 2], [-1, 0, 1]]
|
|
|
|
description: |
|
|
Given an integer array `nums`, return all the triplets `[nums[i], nums[j], nums[k]]` such that `i != j`, `i != k`, and `j != k`, and `nums[i] + nums[j] + nums[k] == 0`.
|
|
|
|
Notice that the solution set must **not contain duplicate triplets**.
|
|
|
|
constraints: |
|
|
- `3 <= nums.length <= 3000`
|
|
- `-10^5 <= nums[i] <= 10^5`
|
|
|
|
examples:
|
|
- input: "nums = [-1,0,1,2,-1,-4]"
|
|
output: "[[-1,-1,2],[-1,0,1]]"
|
|
explanation: "The distinct triplets that sum to zero are [-1,-1,2] and [-1,0,1]."
|
|
- input: "nums = [0,1,1]"
|
|
output: "[]"
|
|
explanation: "No triplet sums to zero."
|
|
- input: "nums = [0,0,0]"
|
|
output: "[[0,0,0]]"
|
|
explanation: "The only triplet [0,0,0] sums to zero."
|
|
|
|
explanation:
|
|
intuition: |
|
|
Finding three numbers that sum to zero seems complex, but we can reduce it to a simpler problem we already know how to solve.
|
|
|
|
Think of it like this: if we **fix** one number (call it `a`), then we need to find two numbers that sum to `-a`. This is exactly the Two Sum problem! But instead of using a hash map (which makes duplicate handling tricky), we can use two pointers on a **sorted** array.
|
|
|
|
Sorting gives us two superpowers:
|
|
1. **Two pointers work**: With a sorted array, if our sum is too small, move left pointer right; if too big, move right pointer left
|
|
2. **Easy duplicate skipping**: Adjacent duplicates become neighbours, so `if nums[i] == nums[i-1]: skip`
|
|
|
|
The algorithm: for each element `nums[i]`, use two pointers on the remaining array to find pairs summing to `-nums[i]`.
|
|
|
|
approach: |
|
|
We solve this using **Sort + Two Pointers**:
|
|
|
|
**Step 1: Sort the array**
|
|
|
|
- Sorting enables two-pointer technique and easy duplicate detection
|
|
- Time: O(n log n), which doesn't affect overall O(n²) complexity
|
|
|
|
|
|
|
|
**Step 2: Fix the first element and find pairs**
|
|
|
|
- For each `i` from 0 to n-3:
|
|
- Skip if `nums[i] == nums[i-1]` (avoid duplicate triplets)
|
|
- **Early termination**: If `nums[i] > 0`, stop — no triplet can sum to zero (all remaining elements are positive)
|
|
- Set `left = i + 1`, `right = n - 1`
|
|
- Find pairs using two pointers
|
|
|
|
|
|
|
|
**Step 3: Two-pointer search for each fixed element**
|
|
|
|
- Calculate `total = nums[i] + nums[left] + nums[right]`
|
|
- If `total < 0`: we need larger values, move `left` right
|
|
- If `total > 0`: we need smaller values, move `right` left
|
|
- If `total == 0`: found a triplet!
|
|
- Add `[nums[i], nums[left], nums[right]]` to result
|
|
- Skip duplicates for both pointers: `while nums[left] == nums[left+1]: left++`
|
|
- Move both pointers inward
|
|
|
|
|
|
|
|
**Step 4: Return all unique triplets**
|
|
|
|
Duplicate skipping happens at three levels: the outer loop, left pointer, and right pointer.
|
|
|
|
common_pitfalls:
|
|
- title: Not Handling Duplicates Properly
|
|
description: |
|
|
Without careful duplicate skipping, you'll return duplicate triplets like `[-1,-1,2]` multiple times.
|
|
|
|
Duplicates must be handled at **all three levels**:
|
|
1. Outer loop: `if i > 0 and nums[i] == nums[i-1]: continue`
|
|
2. Left pointer: `while left < right and nums[left] == nums[left+1]: left += 1`
|
|
3. Right pointer: `while left < right and nums[right] == nums[right-1]: right -= 1`
|
|
wrong_approach: "Using a set of tuples (works but slower)"
|
|
correct_approach: "Skip adjacent duplicates at each level"
|
|
|
|
- title: Duplicate Skipping Order After Finding Triplet
|
|
description: |
|
|
After finding a valid triplet, skip duplicates **before** moving both pointers. A common bug is skipping duplicates incorrectly, leading to missing triplets or infinite loops.
|
|
|
|
The sequence should be: (1) add triplet, (2) skip left duplicates, (3) skip right duplicates, (4) move both `left++` and `right--`.
|
|
wrong_approach: "Moving pointers before skipping duplicates"
|
|
correct_approach: "Skip duplicates first, then move both pointers"
|
|
|
|
- title: Missing Early Termination
|
|
description: |
|
|
Once `nums[i] > 0` in a sorted array, no valid triplet can exist (all remaining elements are non-negative, so the smallest possible sum is positive).
|
|
|
|
This optimisation can significantly speed up cases with many positive numbers.
|
|
wrong_approach: "Continuing to search when nums[i] > 0"
|
|
correct_approach: "if nums[i] > 0: break"
|
|
|
|
key_takeaways:
|
|
- "**Reduce N-sum to (N-1)-sum**: Fix one element and solve a smaller problem — this pattern extends to 4Sum, kSum"
|
|
- "**Sorting enables two pointers**: Transforms O(n²) lookup per element into O(n)"
|
|
- "**Multi-level duplicate handling**: When returning all unique solutions, handle duplicates at every decision point"
|
|
- "**Time complexity is O(n²)**: Can't do better when returning all triplets (there can be O(n²) triplets)"
|
|
|
|
time_complexity: "O(n²). Sorting is O(n log n), then for each of n elements, the two-pointer search is O(n)."
|
|
space_complexity: "O(log n) to O(n). Depends on the sorting algorithm — O(log n) for in-place sorts, O(n) for others. The output is not counted as extra space."
|
|
|
|
solutions:
|
|
- approach_name: Sort + Two Pointers
|
|
is_optimal: true
|
|
code: |
|
|
def three_sum(nums: list[int]) -> list[list[int]]:
|
|
nums.sort() # Enable two pointers and duplicate detection
|
|
result = []
|
|
n = len(nums)
|
|
|
|
for i in range(n - 2):
|
|
# Skip duplicates for the first element
|
|
if i > 0 and nums[i] == nums[i - 1]:
|
|
continue
|
|
|
|
# Early termination: if smallest element is positive, no solution
|
|
if nums[i] > 0:
|
|
break
|
|
|
|
# Two pointers for the remaining array
|
|
left, right = i + 1, n - 1
|
|
|
|
while left < right:
|
|
total = nums[i] + nums[left] + nums[right]
|
|
|
|
if total < 0:
|
|
# Need larger sum, move left pointer
|
|
left += 1
|
|
elif total > 0:
|
|
# Need smaller sum, move right pointer
|
|
right -= 1
|
|
else:
|
|
# Found a triplet
|
|
result.append([nums[i], nums[left], nums[right]])
|
|
|
|
# Skip duplicates for left pointer
|
|
while left < right and nums[left] == nums[left + 1]:
|
|
left += 1
|
|
# Skip duplicates for right pointer
|
|
while left < right and nums[right] == nums[right - 1]:
|
|
right -= 1
|
|
|
|
# Move both pointers for next pair
|
|
left += 1
|
|
right -= 1
|
|
|
|
return result
|
|
explanation: |
|
|
**Time Complexity:** O(n²) — O(n log n) sort + O(n) two-pointer search for each of O(n) elements.
|
|
|
|
**Space Complexity:** O(log n) to O(n) — Sorting space; output not counted.
|
|
|
|
We sort the array, then for each element, use two pointers to find pairs that complete the triplet. Careful duplicate skipping at all three levels ensures we return only unique triplets.
|