Logo for ammarahmed.ca
Medium
May 27, 2025
#two-pointers
#math

15. 3Sum

Problem

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:

1Input: nums = [-1,0,1,2,-1,-4] 2Output: [[-1,-1,2],[-1,0,1]] 3Explanation: 4nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0. 5nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0. 6nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0. 7The distinct triplets are [-1,0,1] and [-1,-1,2]. 8Notice that the order of the output and the order of the triplets does not matter. 9

Example 2:

1Input: nums = [0,1,1] 2Output: [] 3Explanation: The only possible triplet does not sum up to 0. 4

Example 3:

1Input: nums = [0,0,0] 2Output: [[0,0,0]] 3Explanation: The only possible triplet sums up to 0. 4

Constraints:

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

      Approach

      To solve this, we can use a similar approach to Two Sum II - Input Array Sorted.

      The idea is that for each number, we want to run the algorithm for Two Sum II on the remaining elements using the current number's negation as the target. However, this will require sorting the input array to start. We don't need to worry about the elements before the current number because the problem states to return the distinct triplets.

      Let's go through an example with Example 1:

      We have the input array: [-1, 0, 1, 2, -1, 4]. After sorting, we have: [-1, -1, 0, 1, 2, 4].

      Now, for the first iteration, we're treating, arr[0] = -1 as our current number (target = 1) and we'll run the two-pointer algorithm on the remaining elements: [-1, 0, 1, 2, 4]. In other words, we're looking in that subset for two numbers that add up to 1 because then the triplet will add up to 0. We can set up our two pointers and increment/decrement based upon whether the current target is less than or greater than the target. We'll find that -1, 2 add up to 1 (target) so we can add that triplet to our list. We'll continue this way until we finish the iteration. See Two Sum II - Input Array Sorted for details on the algorithm we're running on the subset.

      Another important note is in regards to duplicate triplets. We can mitigate this by skipping over numbers we've already processed. Since the array is sorted, all duplicates will be beside eachother. Therefore, in the main loop, we can check if the next number is the same as the current and skip the current. In the inner loop, when we find a triplet, we can keep incrementing/decrementing until we are at the last occurrence of the particular number before moving on to finding any more triplets with that target.

      Complexity

      Time: O(n^2)

      Sorting the input array is n log n. After that we iterate over the array twice, which is O(n^2). Adding them together, we get O(n log n) + O(n^2) = O(n^2) (we drop lower order terms).

      Space: O(1)

      No extra space is created.

      Solution

      1import "slices" 2func threeSum(nums []int) [][]int { 3 slices.Sort(nums) 4 var ans [][]int 5 for i, curr := range nums { 6 if i > 0 && nums[i] == nums[i - 1] { 7 continue 8 } 9 target := curr * -1 10 l := i + 1 11 r := len(nums) - 1 12 for l < r { 13 pot := nums[l] + nums[r] 14 if pot == target { 15 ans = append(ans, []int{curr, nums[l], nums[r]}) 16 for l < r && nums[l] == nums[l + 1] { 17 l++ 18 } 19 for l < r && nums[r] == nums[r - 1] { 20 r-- 21 } 22 l++ 23 r-- 24 } else if pot < target { 25 l++ 26 } else { 27 r-- 28 } 29 } 30 } 31 return ans 32}