Blind75 - 3Sum (The Pitfall of Duplicates & Two Pointer Efficiency)
3Sum
Given an array nums, find all unique triplets that sum to zero.
- Duplicate combinations must not be included in the result.
- The order of the answer does not matter.
- An efficient solution around $O(N^2)$ is expected.
Initial Approach and Trial-and-Error (Logic Check)
The first attempts involved using set() to remove duplicates or tracking used indices, but both led to logical flaws and performance issues.
Analysis of the Failing Initial Code
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums = list(set(nums)) # Bug 1: Cannot form triplets like [0,0,0]
answer = []
n = len(nums)
used = [False] * n
for i in range(n):
if used[i]: continue
for j in range(i+1, n):
if used[j]: continue
twoSum = nums[i] + nums[j]
if (-twoSum) in nums:
used[i], used[j] = True, True # Bug 2: A number can be part of multiple valid combinations
answer.append([nums[i], nums[j], -twoSum])
return answer
Logic Errors and Performance Analysis
- Data loss: Applying
set(nums)makes it impossible to solve cases like[-1, -1, 2]where the same number must be used twice. - Pointer isolation: Locking a number with
used[i] = Trueprevents it from being found in other valid combinations. - Time complexity: The
inoperation inside nestedforloops results in near-$O(N^3)$ performance, causing Time Limit Exceeded on large datasets.
Solution Strategy: Sorting and Two Pointers
The most efficient approach is to sort the array, fix one anchor point, and narrow the remaining two points inward from both ends using a two-pointer strategy.
The Core Deduplication Logic: i > 0 and nums[i] == nums[i-1]
This condition is the most elegant way to avoid duplicate results.
- Granting the first chance: When
i=0,i > 0evaluates toFalse, so theifstatement is bypassed and the computation proceeds. (Short-circuit evaluation) - Blocking subsequent duplicates: From
i=1onward, if the current value equals the previous one (nums[i-1]), the combination was already processed in a prior iteration, so it is skipped withcontinue. - Solving the [0, 0, 0] case: With this logic, the first
0serves as the anchor, while the remaining two0s are found by the pointers (left,right), correctly returning[[0, 0, 0]].
Final Optimized Code
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
answer = []
n = len(nums)
for i in range(n - 2):
# Skip duplicate anchor points (key lesson)
if i > 0 and nums[i] == nums[i - 1]:
continue
left, right = i + 1, n - 1
while left < right:
total = nums[i] + nums[left] + nums[right]
if total < 0:
left += 1
elif total > 0:
right -= 1
else:
answer.append([nums[i], nums[left], nums[right]])
# Skip duplicate elements for left and right
while left < right and nums[left] == nums[left + 1]:
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1
return answer
Summary & Reflection
Why Is Brute Force ($O(N^3)$) Bad?
- Space-time trade-off: Simply dumping results into a
set()is easy to implement but wastes unnecessary computation and memory (Set storage). - Algorithmic elegance: The two-pointer approach maintains $O(1)$ space complexity (excluding the result array) while optimizing time complexity to $O(N^2)$.
Key Lesson
“Grant the first opportunity, but screen from the second onward.”
Rather than blindly removing data with set to handle duplicates, controlling the flow by comparing with the previous index in a sorted state is far safer and more efficient. Additionally, leveraging Python’s short-circuit evaluation of and prevents index errors while keeping the logic concise.
Leave a comment