3Sum
Find every unique triplet in an array summing to zero. Sort then two-pointer scan per anchor.
5 min read
two-pointers array sorting
Problem#
Given an integer array nums, return all unique triplets [a, b, c] such that a + b + c == 0. The result must not contain duplicate triplets.
Examples#
[-1, 0, 1, 2, -1, -4]→[[-1, -1, 2], [-1, 0, 1]][0, 1, 1]→[][0, 0, 0]→[[0, 0, 0]]
Constraints#
3 <= nums.length <= 3000-10^5 <= nums[i] <= 10^5
Hints#
Hint 1
Sort first — it converts “find pair summing to X” into a two-pointer linear scan.
Hint 2
Skip duplicates at every level (anchor, left, right) to dedupe without a hash set.
Approach#
Hash-set per anchor — O(n²) time, O(n) space, dedup pain via set-of-tuples.
Sort + two-pointer per anchor — O(n²) time, O(1) extra space, dedup is a one-line skip.
Solution#
class Solution {public: vector<vector<int>> threeSum(vector<int>& nums) { sort(nums.begin(), nums.end()); vector<vector<int>> ans; const int n = nums.size(); for (int i = 0; i < n - 2; ++i) { if (nums[i] > 0) break; // remaining sums > 0 if (i > 0 && nums[i] == nums[i - 1]) continue; // dedupe anchor int l = i + 1, r = n - 1; while (l < r) { int sum = nums[i] + nums[l] + nums[r]; if (sum < 0) ++l; else if (sum > 0) --r; else { ans.push_back({nums[i], nums[l], nums[r]}); while (l < r && nums[l] == nums[l + 1]) ++l; while (l < r && nums[r] == nums[r - 1]) --r; ++l; --r; } } } return ans; }};func threeSum(nums []int) [][]int { sort.Ints(nums) ans := [][]int{} n := len(nums) for i := 0; i < n-2; i++ { if nums[i] > 0 { break } if i > 0 && nums[i] == nums[i-1] { continue } l, r := i+1, n-1 for l < r { sum := nums[i] + nums[l] + nums[r] if sum < 0 { l++ } else if sum > 0 { r-- } else { ans = append(ans, []int{nums[i], nums[l], nums[r]}) for l < r && nums[l] == nums[l+1] { l++ } for l < r && nums[r] == nums[r-1] { r-- } l++ r-- } } } return ans}from typing import List
class Solution: def threeSum(self, nums: List[int]) -> List[List[int]]: nums.sort() ans: List[List[int]] = [] n = len(nums) for i in range(n - 2): if nums[i] > 0: break if i > 0 and nums[i] == nums[i - 1]: continue l, r = i + 1, n - 1 while l < r: s = nums[i] + nums[l] + nums[r] if s < 0: l += 1 elif s > 0: r -= 1 else: ans.append([nums[i], nums[l], nums[r]]) while l < r and nums[l] == nums[l + 1]: l += 1 while l < r and nums[r] == nums[r - 1]: r -= 1 l += 1 r -= 1 return ansvar threeSum = function(nums) { nums.sort((a, b) => a - b); const ans = []; const n = nums.length; for (let i = 0; i < n - 2; i++) { if (nums[i] > 0) break; if (i > 0 && nums[i] === nums[i - 1]) continue; let l = i + 1, r = n - 1; while (l < r) { const sum = nums[i] + nums[l] + nums[r]; if (sum < 0) l++; else if (sum > 0) r--; else { ans.push([nums[i], nums[l], nums[r]]); while (l < r && nums[l] === nums[l + 1]) l++; while (l < r && nums[r] === nums[r - 1]) r--; l++; r--; } } } return ans;};class Solution { public List<List<Integer>> threeSum(int[] nums) { Arrays.sort(nums); List<List<Integer>> ans = new ArrayList<>(); int n = nums.length; for (int i = 0; i < n - 2; i++) { if (nums[i] > 0) break; if (i > 0 && nums[i] == nums[i - 1]) continue; int l = i + 1, r = n - 1; while (l < r) { int sum = nums[i] + nums[l] + nums[r]; if (sum < 0) l++; else if (sum > 0) r--; else { ans.add(Arrays.asList(nums[i], nums[l], nums[r])); while (l < r && nums[l] == nums[l + 1]) l++; while (l < r && nums[r] == nums[r - 1]) r--; l++; r--; } } } return ans; }}function threeSum(nums: number[]): number[][] { nums.sort((a, b) => a - b); const ans: number[][] = []; const n = nums.length; for (let i = 0; i < n - 2; i++) { if (nums[i] > 0) break; if (i > 0 && nums[i] === nums[i - 1]) continue; let l = i + 1, r = n - 1; while (l < r) { const sum = nums[i] + nums[l] + nums[r]; if (sum < 0) l++; else if (sum > 0) r--; else { ans.push([nums[i], nums[l], nums[r]]); while (l < r && nums[l] === nums[l + 1]) l++; while (l < r && nums[r] === nums[r - 1]) r--; l++; r--; } } } return ans;}Editorial#
Sorting unlocks two-pointer convergence: with nums[i] fixed, you need a pair summing to -nums[i], and the sorted invariant says moving l right increases the sum, moving r left decreases it. The early nums[i] > 0 break is tight: if the smallest element of the triplet is positive, no zero-sum triplet exists. The three “skip duplicates” guards are independent — they handle the same value appearing as anchor, as left, and as right respectively.
Complexity#
- Time: O(n²) — n anchors × linear two-pointer pass.
- Space: O(1) ignoring output.
Concept revision#
Related#