LeetCode Meditations: 3Sum
Let's start with the description for this one:
Given an integer array
nums
, return all the triplets[nums[i], nums[j], nums[k]]
such thati != j
,i != k
, andj != k
, andnums[i] + nums[j] + nums[k] == 0
.Notice that the solution set must not contain duplicate triplets.
For example:
threeSum([-1, 0, 1, 2, -1, -4]);
// -> [ [-1, -1, 2], [-1, 0, 1] ]
threeSum([0, 1, 1]);
// []
threeSum([0, 0, 0]);
// [ [0, 0, 0] ]
threeSum([-1, 0, 1, 2, -1, -4]);
// -> [ [-1, -1, 2], [-1, 0, 1] ]
threeSum([0, 1, 1]);
// []
threeSum([0, 0, 0]);
// [ [0, 0, 0] ]
First of all, let's admit, this problem is a bit challenging. The first thing that comes to mind is that we can brute force our way to find all three. But in order to do that, we need to create three nested loops, which is not a good idea. Still, we can try it, but beware; it is terrifying:
function threeSum(nums: number[]): number[][] {
let s: Set<string> = new Set();
for (let i = 0; i < nums.length; i++) {
for (let j = i + 1; j < nums.length; j++) {
for (let k = j + 1; k < nums.length; k++) {
if (nums[i] + nums[j] + nums[k] === 0) {
let triplets = JSON.stringify([nums[i], nums[j], nums[k]].sort((a, b) => a - b));
if (!s.has(triplets)) {
s.add(triplets)
}
}
}
}
}
return [...s].map(item => JSON.parse(item));
};
function threeSum(nums: number[]): number[][] {
let s: Set<string> = new Set();
for (let i = 0; i < nums.length; i++) {
for (let j = i + 1; j < nums.length; j++) {
for (let k = j + 1; k < nums.length; k++) {
if (nums[i] + nums[j] + nums[k] === 0) {
let triplets = JSON.stringify([nums[i], nums[j], nums[k]].sort((a, b) => a - b));
if (!s.has(triplets)) {
s.add(triplets)
}
}
}
}
}
return [...s].map(item => JSON.parse(item));
};
It passes the most of the tests, but as expected, it results in a Time Limit Exceeded error in one of them. And, it has a time complexity.
So, let's take a deep breath and look at another approach.
We can start with the sorted version of nums
. Then, starting with the first number as the first value for the three numbers that add up to 0
, we can use the Two Pointers technique to find the rest of the two values.
function threeSum(nums: number[]): number[][] {
let result: number[][] = [];
nums = nums.sort((a, b) => a - b);
for (let i = 0; i < nums.length; i++) {
// Ignore the number if it's not the first value and a duplicate
if (i > 0 && nums[i] === nums[i - 1]) {
continue;
}
let left = i + 1;
let right = nums.length - 1;
while (left < right) {
let sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {
right--;
} else if (sum < 0) {
left++;
} else {
result.push([nums[i], nums[left], nums[right]]);
left++;
while (nums[left] === nums[left - 1] && left < right) {
left++;
}
}
}
}
return result;
};
function threeSum(nums: number[]): number[][] {
let result: number[][] = [];
nums = nums.sort((a, b) => a - b);
for (let i = 0; i < nums.length; i++) {
// Ignore the number if it's not the first value and a duplicate
if (i > 0 && nums[i] === nums[i - 1]) {
continue;
}
let left = i + 1;
let right = nums.length - 1;
while (left < right) {
let sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {
right--;
} else if (sum < 0) {
left++;
} else {
result.push([nums[i], nums[left], nums[right]]);
left++;
while (nums[left] === nums[left - 1] && left < right) {
left++;
}
}
}
}
return result;
};
The outer for
loop picks a value for the first number to make the sum. Then, with left
and right
pointers, we check if the sum equals our target value of 0
, if it's greater than 0
, we decrement right
to find a smaller value for that position; and if the sum is less than 0
, we increment left
to find a greater value. Otherwise, if we find a triplet that adds up to 0
, we add it to our result
array, and continue incrementing left
. Note that we need another while
loop at this point to ignore a duplicate.
This solution comes from NeetCode, who does a great explanation of it in this video.
Time and space complexity
The time complexity for this solution is because as we iterate through each item, we have an inner loop (while (left < right)
) that iterates through items in the worst case.
Note that we have a sorting operation that takes time, but in this case, will dominate.
The space complexity is as we don't need additional space.
Next up is the problem Container with Most Water, until then, happy coding.