Find Unique Triplets in an Integer Array That Sum to Zero

Nitish Kumar Singh

Mar 1, 2025

Given an array of integers nums, find all unique triplets in nums that sum up to zero, where all elements in a triplet are different elements from the array. This is the 3Sum problem on LeetCode.

Photo by Benjamin Wong on Unsplash

For example, given nums = [-1, 1, 0, 2, -1, -4], the unique triplets are [[ -1, -1, 2 ], [ -1, 0, 1 ]], because:

  • nums[0] + nums[1] + nums[2] = -1 + 1 + 0 = 0
  • nums[0] + nums[3] + nums[4] = -1 + 2 - 1 = 0
  • nums[1] + nums[2] + nums[4] = 1 + 0 - 1 = 0

There are three triplets that sum up to zero, but [-1,1,0] and [1,0,-1] are duplicates. Therefore, there are only two unique triplets: [[ -1, -1, 2 ], [ -1, 0, 1 ]].

Constraints:

  • 3 <= nums.length <= 3000
  • -105 <= nums[i] <= 105

We will solve this problem using the two-pointer approach. First, we sort the array, run an outer for loop from 0 to n-1, and an inner while loop until left < right with the initial values of left = i + 1 and right = n - 1.

The complete code for solving this problem is below:

public List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> result = new ArrayList<>();
    Arrays.sort(nums);
    for(int i=0; i<nums.length-2<=0;i++){
        if(i>0 && nums[i] == nums[i-1]) continue;
        int left = i + 1, right = nums.length - 1;
        while (left < right) {
            int sum = nums[i] + nums[left] + nums[right];
            if (sum == 0) {
                result.add(Arrays.asList(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 result;
}

Explanation:

In the above code, we consider elements at indices i, left, and right as the three items of a possible triplet. Since these three indices are never the same, and the statement nums[i] + nums[left] + nums[right] == 0 ensures a valid triplet, the approach works correctly.

We have added the following three lines to ensure the uniqueness of each triplet:

// This line avoid adding same 1st item of triplet
if(i>0 && nums[i] == nums[i-1]) continue;

// These two lines avoid adding same 2nd and 3rd item of triplet
while (left < right && nums[left] == nums[left + 1]) left++;
while (left < right && nums[right] == nums[right - 1]) right--;

However, we do not need to check for a duplicate third item because if a triplet contains two unique elements, it is already unique, even if the third item repeats.

If i = 3 for the sorted nums = [-4, -1, -1, 0, 1, 2, 4, 7, 9], all numbers after i = 3 are positive, meaning three positive numbers can never sum up to zero. So, we can stop the outer loop early by adding this condition:

for(int i=0; i<nums.length-2 && nums[i]<=0;i++){

When sum < 0, we increase left because we need a larger value to increase the sum. Decreasing right would give us a smaller value, which is incorrect since the array is sorted. Similarly, when sum > 0, we decrease right to get a smaller value.

This approach has a time complexity of O(n²) + O(n log n). Since O(n log n) (sorting) is negligible compared to O(n²), the overall complexity is O(n²).

Alternative Approach Using Lazy Evaluation

We can further optimize runtime by using a different way to find triplets using an anonymous subclass of AbstractList. This improves performance and memory efficiency using lazy evaluation (deferring computation until necessary).

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
		return new AbstractList<>() {
			List<List<Integer>> results;

			@Override
			public int size() {
				if (results == null)
					results = getResults(nums);
				return results.size();
			}

			@Override
			public List<Integer> get(int index) {
				if (results == null)
					results = getResults(nums);
				return results.get(index);
			}
		};
	}

	private List<List<Integer>> getResults(int[] nums) {
		List<List<Integer>> results = new ArrayList<>();
		int len = nums.length;
		Arrays.sort(nums);
		for (int i = 0; i < len - 2 && nums[i] <= 0; ++i) {
			if (i > 0 && nums[i] == nums[i - 1])
				continue;
			twoSum(results, nums, i + 1, len - 1, -nums[i]);
		}
		return results;
	}

	private void twoSum(List<List<Integer>> results, int[] nums, int left, int right, int target) {
		while (left < right) {
			if (nums[left] + nums[right] > target) {
				--right;
				continue;
			}
			if (nums[left] + nums[right] < target) {
				++left;
				continue;
			}

			results.add(Arrays.asList(-target, nums[left++], nums[right--]));

			while (left <= right && nums[left] == nums[left - 1])
				++left;
		}
	}
}

How This Works:

  • This code works similar to the first one but implements lazy evaluation, which improves performance by deferring computation until absolutely necessary and optimizing memory usage.
  • It splits the outer and inner loops of the standard two-pointer method into two separate methodsgetResults() (outer loop) and twoSum() (inner loop).

This is the standard and optimized two-pointer approach for solving the 3Sum problem. I hope you understand each line of code and the logic behind this approach.

Happy Coding!

Published on Mar 1, 2025
Comments (undefined)

Read More