2 POINTERS › TWO POINTER ON ARRAYS
Open — the attempt gate is not wired up yet
This editorial is meant to unlock after you have run the problem at least once, with the worked solution behind one further deliberate click. That needs per-learner unlock state nothing stores today, so for now the whole article is open.
Try it yourself first →A triplet is determined by choosing its smallest, middle, and largest values after sorting. Fix the first value at index i, then search for two later values whose sum is -nums[i]. Because the remaining values are sorted, the two-pointer positions describe a complete search interval rather than an arbitrary collection of pairs.
If the current three-value sum is too small, moving the left pointer right is the only useful move: every value before it is no larger and would keep the sum too small. If the sum is too large, moving the right pointer left is the only useful move. When the sum is zero, record it and skip equal values so the same value triplet cannot return through different indices.
| MEASURE | BOUND | WHY |
|---|---|---|
| Time | O(n^2) | Sorting costs O(n log n). For each of the O(n) fixed positions, left and right only move inward, so that inner scan performs at most O(n) pointer moves; the scans therefore dominate sorting and do not revisit a discarded position. |
| Space | O(1) extra | The algorithm uses only the fixed index and two pointers beyond the result and the sorting implementation's working memory. The returned triplets are required output and are excluded. If the sorting implementation uses recursion, its stack can add O(log n) space in the usual case, while the two-pointer state itself remains constant. |
#include <algorithm>
#include <vector>
using namespace std;
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
int n = nums.size();
for (int i = 0; i < n - 2; ++i) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
int left = i + 1;
int right = n - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
res.push_back({nums[i], nums[left], nums[right]});
while (left < right && nums[left] == nums[left + 1]) {
++left;
}
while (left < right && nums[right] == nums[right - 1]) {
--right;
}
++left;
--right;
} else if (sum < 0) {
++left;
} else {
--right;
}
}
}
return res;
}
};The two duplicate checks happen at different levels. The check on i prevents repeating an entire search for the same first value. The checks after a match prevent repeating the same second or third value within that search. The final left and right increments are still necessary after skipping duplicates, because otherwise the loop would remain on the values that just produced the recorded triplet.
You can keep the sort and replace the inward pair scan with a set for each fixed i. Walk j from i + 1 to the end and ask whether the needed third value, -nums[i] - nums[j], has already appeared in this scan. Since the array is sorted, skipping repeated j values is enough to avoid duplicate triplets for the fixed i.
#include <algorithm>
#include <unordered_set>
#include <vector>
using namespace std;
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
int n = nums.size();
for (int i = 0; i < n - 2; ++i) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
unordered_set<int> seen;
for (int j = i + 1; j < n; ++j) {
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
int needed = -nums[i] - nums[j];
if (seen.count(needed)) {
res.push_back({nums[i], needed, nums[j]});
}
seen.insert(nums[j]);
}
}
return res;
}
};This is a different arrangement, not an asymptotic improvement. It still takes O(n^2) average time after sorting, but each fixed value creates a set that can hold O(n) values, so the extra space becomes O(n) rather than the two-pointer state's O(1). The two-pointer solution is usually preferable because it avoids hashing and gives a deterministic movement proof.