3Sum - Leetcode 15

Problem Statement

Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.

Notice that the solution set must not contain duplicate triplets.

Example 1:

Input: nums = [-1,0,1,2,-1,-4]
Output: [[-1,-1,2],[-1,0,1]]

Problem Link

Brute force Approach

We need 3 numbers with distinct indices. So we found all the triplets that can be formed from the array. Three for loops so O(n^3). We sort the elements while adding them to the answer set so that duplicates can be avoided.

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Set<List<Integer>> set = new HashSet<>();

        for(int i=0; i< nums.length; i++){
            for(int j=i+1; j<nums.length; j++){
                for(int k=j+1; k<nums.length; k++){
                    if (nums[i]+nums[j]+nums[k]==0){
                        List<Integer> triplet = Arrays.asList(nums[i],nums[j],nums[k]);
                        Collections.sort(triplet);
                        set.add(triplet);
                    }
                }
            }
        }
        return new ArrayList<>(set);
    }
}

Approach (set) O(n^2)

In this method, we eliminate the inner for loop and instead utilize two for loops to iterate through two sets of numbers. The third number can be calculated as the complement of the sum of nums1 and nums2 from zero. We then check if this third number exists among the elements encountered during the previous traversal of arrays. If the element is found, we include it in the result. To optimize efficiency, we employ a temporary set to store all encountered elements, facilitating the search for the third element within this set.

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Set<List<Integer>> set = new HashSet<>();

        for(int i=0; i< nums.length; i++){
            Set<Integer> tempSet = new HashSet<>();
            for(int j=i+1; j<nums.length; j++){
                int thirdValue = -(nums[i] + nums[j]);
                if(tempSet.contains(thirdValue)){
                    tempSet.remove(thirdValue);
                    List<Integer> triplet = Arrays.asList(nums[i],nums[j],thirdValue);
                    Collections.sort(triplet);
                    set.add(triplet);
                } else {
                    tempSet.add(nums[j]);
                }
            }
        }
        return new ArrayList<>(set);
    }
}

An approach without a set (3 Pointer method) O(n^2) + O(nlogn)

First sort the array. Now consider three pointers: i, j, and k. While keeping i and j fixed on the left side of the array, position k at the array's end.

Next, we calculate the sum of this triplet and check if it equals zero.

  1. If the total sum is greater than zero, it implies that one value within the triplet should be reduced. This reduction can only be achieved by shifting the k pointer to the left (i.e., decreasing its position).

  2. Conversely, if the total sum is less than zero, it indicates that one value within the triplet should be increased. To accomplish this, we move both i and j. To maintain the sequence of triplets, we lock the position of i, causing j to shift to the right and consequently increasing the total sum value.

  3. Optional: To optimize the algorithm's efficiency and avoid unnecessary comparisons, we skip over elements that repeat and where the answer is already known to be zero. We apply this optimization to both j and k. Similarly, for index i, we follow the same strategy because finding triplets for a distinct element will yield the same triplets again.

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Set<List<Integer>> set = new HashSet<>();
        Arrays.sort(nums);

        for(int i=0; i<nums.length; i++) {
            if(i>0 && nums[i-1]==nums[i]) continue;

            int j = i+1;
            int k = nums.length-1;
            while(j<k){
                int total = nums[i] + nums[j] + nums[k];
                if(total<0) {
                    j+=1;
                } else if(total > 0) {
                    k-=1;
                } else {
                    List<Integer> triplet = Arrays.asList(nums[i], nums[j], nums[k]);
                    set.add(triplet);
                    j+=1;
                    k-=1;
                    while(j<k && nums[j]==nums[j-1]) j++;
                    while(j<k && nums[k]==nums[k+1]) k--;
                }
            }

        }
        return new ArrayList<>(set);
    }
}

Optional - Another version of 3 pointer approach

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Set<List<Integer>> set = new HashSet<>();
        Arrays.sort(nums);

        int i = 0;
        int j = i+1;
        int k = nums.length-1;
        while( i< nums.length-2){

            int total = nums[i]+nums[j]+nums[k];
            if(total<0){
                while(j<k && nums[j]==nums[j+1]) j++;
                j+=1;
            } else if(total>0){ 
                while(j<k && nums[k-1]==nums[k]) k--;
                k-=1;
            } else{
                List<Integer> triplet = Arrays.asList(nums[i],nums[j],nums[k]);
                set.add(triplet);
                j+=1;
                k-=1;
            }

            if(j>=k){
                while(i<nums.length-1 && nums[i]==nums[i+1]) i++;
                i++;
                j = i+1;
                k = nums.length-1;
            } 

        }

        return new ArrayList<>(set);
    }
}