Problem: 3sum

Posted by Marcy on February 18, 2015

Question

Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.

Note: The solution set must not contain duplicate triplets.

For example, given array S = [-1, 0, 1, 2, -1, -4],

A solution set is:

[
  [-1, 0, 1],
  [-1, -1, 2]
]

Solution

The idea is to sort the input array nums and then run through all indices of a possible first element nums[first] of a triplet. For each possible first element, the problem becomes finding two sums in the sorted array from first+1 to nums.length-1.

To find 2 sums in a sorted array, we could use a bi-dementioanl search. Set two indecies start and end for the start and end of the search range and compare sum of nums[start] + nums[end] and 0 - nums[first]. When sum == 0 - nums[first], we know a valid 2 sum is found. When sum < 0 - nums[first], increase sum by moving start forward. When sum > 0 - nums[first], decrease sum by moving end backwards.

When moving first, start and end, to avoid duplicate results, they need to be moved to the next different element in nums.

Code

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(nums);
        for(int i=0; i<nums.length-2; i++) {
            int first = nums[i];
            List<List<Integer>> sumResult = twoSum(i+1, nums, -first);
            for(List<Integer> res : sumResult) {
                res.add(0, first);
            }
            result.addAll(sumResult);

            // To reduce the duplicate, look for the last same element
            // next i++ will move i to the next different element
            while(nums[i] == nums[i+1] && i<nums.length-2) i++;
        }

        return result;
    }

    public List<List<Integer>> twoSum(int start, int[] nums, int target) {
        List<List<Integer>> twoSums = new ArrayList<>();
        int l = start;
        int r = nums.length-1;
        while(l<r) {
            int sum = nums[l] + nums[r];
            if(sum == target) {
                List<Integer> pair = new ArrayList<>();
                pair.add(nums[l]);
                pair.add(nums[r]);
                twoSums.add(pair);
                # find the next different nums[l]

                // look for the next different element
                l++;
                while(l<r && nums[l] == nums[l-1]) l++;
                # find the next different nums[r]
                r--;
                while(l<r && nums[r] == nums[r+1]) r--;
            }
            else if(sum > target) {
                # find the next different nums[r]
                r--;
                while(l<r && nums[r] == nums[r+1]) r--;
            }
            else {
                # find the next different nums[l]
                l++;
                while(l<r && nums[l] == nums[l-1]) l++;
            }
        }
        
        return twoSums;
    }
}

Performance

Sorting the array will use O(logn) time. And to find the three sums, the outer loop for iterating first takes O(n). The inner loop for finding all the two sums takes O(n). So overall it takes O(logn) + O(n) * O(n) = O(n^2).

For space complexity, it depends on how many triples can be found in nums.

K Sum solution

The above solution could be easily extended to finding k sum. The key idea is to use recursion to reduce the k sum problem to k-1 sum. And when it is reduced to 2 sum, use the bi-dimensional search method to get all the two sums.

Code

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);
        // Use 4sum as example
        return kSum(nums, target, 4, 0);
    }
    
    public List<List<Integer>> kSum(int[] nums, int target, int k, int index) {
        if(k==2) return twoSum(nums, target, index);
        
        List<List<Integer>> results = new ArrayList();
        if(nums.length < k) return new ArrayList();
        for(int i=index; i<nums.length-k+1; i++) {
            List<List<Integer>> kMinusOneSumResults = kSum(nums, target-nums[i], k-1, i+1);
            for(List<Integer> res : kMinusOneSumResults) {
                res.add(0, nums[i]);
                results.add(res);
            }
            
            while(i<nums.length-k+1 && nums[i+1] == nums[i]) i++;
        }
        
        return results;
    }
    
    public List<List<Integer>> twoSum(int[] nums, int target, int index) {
        List<List<Integer>> results = new ArrayList();
        
        int l = index, r = nums.length-1;
        while(l<r) {
            if(nums[l] + nums[r] == target) {
                List<Integer> res = new ArrayList();
                res.add(nums[l]);
                res.add(nums[r]);
                results.add(res);
                l++;
                while(l<r && nums[l] == nums[l-1]) l++;
                r--;
                while(l<r && nums[r] == nums[r+1]) r--;
            }
            else if(nums[l] + nums[r] < target) {
                l++;
            }
            else {
                r--;
            }
        }
        
        return results;
    }
}

Performance

Similar to 3 sum, the time complexity will be O(n^k-1). And space complexity depends on how many k sums can be found in nums