Facebook Pixel

259. 3Sum Smaller 🔒

Problem Description

You are given an array of n integers called nums and an integer target. Your task is to find the number of index triplets (i, j, k) that satisfy the following conditions:

  1. The indices must follow the order: 0 <= i < j < k < n
  2. The sum of the three elements at these indices must be less than the target: nums[i] + nums[j] + nums[k] < target

For example, if nums = [-2, 0, 1, 3] and target = 2, you would need to find all combinations of three different indices where the sum of the corresponding elements is less than 2.

The key insight is that since we only care about the count of valid triplets and not their actual values or original positions, we can sort the array first. After sorting, we can efficiently use a two-pointer technique:

  1. Fix the first element nums[i] by iterating through the array
  2. For each fixed i, use two pointers j and k where j starts right after i and k starts at the end of the array
  3. When nums[i] + nums[j] + nums[k] < target, all elements between j and k would also form valid triplets with i and j (since the array is sorted). This gives us k - j valid triplets at once
  4. Move the pointers accordingly: increment j when the sum is less than target, decrement k when the sum is greater than or equal to target

This approach reduces the time complexity from O(n³) for a brute force solution to O(n²) by cleverly counting multiple valid triplets at once.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The brute force approach would be to check all possible triplets using three nested loops, which would take O(n³) time. We need a smarter way to count valid triplets.

The key observation is that the problem asks for counting triplets where the sum is less than a target value. This is a inequality condition, not an equality. When dealing with inequalities and sums, sorting the array often helps because it creates a predictable order that we can exploit.

Once we sort the array, we can think about fixing one element and finding pairs in the remaining array. This reduces the problem from finding triplets to finding pairs with a modified target (target - nums[i]).

For finding pairs with a sum condition in a sorted array, the two-pointer technique is natural. We place one pointer at the start and another at the end. But here's the crucial insight: when we find that nums[i] + nums[j] + nums[k] < target, we don't just count this one triplet. Since the array is sorted, we know that replacing nums[k] with any element between j and k will also satisfy the condition (because those elements are all smaller than nums[k]).

This means when nums[i] + nums[j] + nums[k] < target, we can immediately count k - j valid triplets: (i, j, j+1), (i, j, j+2), ..., (i, j, k). This batch counting is what makes the algorithm efficient.

The pointer movement logic follows naturally: if the current sum is too small, we need to increase it by moving j right (to get a larger value). If the sum is too large, we decrease it by moving k left (to get a smaller value). This ensures we explore all possible valid combinations without redundancy.

Solution Approach

The implementation follows a sorting + two pointers + enumeration pattern:

Step 1: Sort the array

nums.sort()

Sorting takes O(n log n) time and enables the two-pointer technique to work efficiently.

Step 2: Initialize variables

ans, n = 0, len(nums)

We maintain ans to count valid triplets and store the array length in n.

Step 3: Enumerate the first element

for i in range(n - 2):

We iterate i from 0 to n-3 (inclusive) since we need at least 3 elements for a triplet. This fixes the first element of our triplet as nums[i].

Step 4: Two-pointer search for the remaining pair

j, k = i + 1, n - 1

For each fixed i, initialize two pointers:

  • j starts at i + 1 (the next element after i)
  • k starts at n - 1 (the last element of the array)

Step 5: Process pairs with the two-pointer technique

while j < k:
    x = nums[i] + nums[j] + nums[k]
    if x < target:
        ans += k - j
        j += 1
    else:
        k -= 1

The core logic works as follows:

  • Calculate the sum x = nums[i] + nums[j] + nums[k]
  • If x < target: All elements between indices j+1 and k would also form valid triplets with i and j because:
    • The array is sorted, so nums[j+1] < nums[j+2] < ... < nums[k]
    • Therefore, nums[i] + nums[j] + nums[j+1], nums[i] + nums[j] + nums[j+2], ..., nums[i] + nums[j] + nums[k] are all less than target
    • This gives us exactly k - j valid triplets
    • Move j right to explore new combinations
  • If x >= target: The current sum is too large, so we need to decrease it by moving k left to get a smaller value

Time Complexity: O(n²) - The outer loop runs O(n) times, and for each iteration, the two-pointer search takes O(n) time.

Space Complexity: O(1) - We only use a constant amount of extra space (not counting the space used for sorting, which depends on the implementation).

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with nums = [-2, 0, 1, 3] and target = 2.

Step 1: Sort the array The array is already sorted: [-2, 0, 1, 3]

Step 2: Fix the first element and use two pointers

Iteration 1: i = 0 (first element = -2)

  • Initialize: j = 1 (points to 0), k = 3 (points to 3)
  • Calculate sum: -2 + 0 + 3 = 1 < 2 ✓
    • All triplets with i=0, j=1, and any k from j+1 to 3 are valid
    • Count: k - j = 3 - 1 = 2 valid triplets
    • These are: (-2, 0, 1) and (-2, 0, 3)
    • Move j right: j = 2
  • Now j = 2 (points to 1), k = 3 (points to 3)
  • Calculate sum: -2 + 1 + 3 = 2 >= 2 ✗
    • Sum is not less than target, move k left: k = 2
  • Now j = 2, k = 2, so j >= k, exit inner loop

Iteration 2: i = 1 (first element = 0)

  • Initialize: j = 2 (points to 1), k = 3 (points to 3)
  • Calculate sum: 0 + 1 + 3 = 4 >= 2 ✗
    • Sum is too large, move k left: k = 2
  • Now j = 2, k = 2, so j >= k, exit inner loop

Iteration 3: i = 2 would leave no room for j and k, so we stop.

Total count: 2

The two valid triplets found are:

  1. Indices (0, 1, 2): nums[0] + nums[1] + nums[2] = -2 + 0 + 1 = -1 < 2
  2. Indices (0, 1, 3): nums[0] + nums[1] + nums[3] = -2 + 0 + 3 = 1 < 2

Note how the algorithm efficiently counted both valid triplets in one step when we found that -2 + 0 + 3 < 2, immediately recognizing that replacing 3 with any smaller element (in this case, 1) would also satisfy the condition.

Solution Implementation

1class Solution:
2    def threeSumSmaller(self, nums: List[int], target: int) -> int:
3        # Sort the array to enable two-pointer technique
4        nums.sort()
5      
6        # Initialize count and get array length
7        count = 0
8        n = len(nums)
9      
10        # Fix the first element and use two pointers for the remaining elements
11        for i in range(n - 2):
12            # Initialize two pointers: left starts right after i, right starts at the end
13            left = i + 1
14            right = n - 1
15          
16            # Use two-pointer technique to find valid triplets
17            while left < right:
18                # Calculate the sum of current triplet
19                current_sum = nums[i] + nums[left] + nums[right]
20              
21                if current_sum < target:
22                    # If sum is less than target, all elements between left and right
23                    # form valid triplets with nums[i] and nums[left]
24                    # This is because array is sorted, so nums[left] + nums[left+1...right-1] + nums[i]
25                    # will all be less than current_sum
26                    count += right - left
27                    # Move left pointer to explore new combinations
28                    left += 1
29                else:
30                    # If sum is greater than or equal to target, 
31                    # we need a smaller sum, so move right pointer left
32                    right -= 1
33      
34        return count
35
1class Solution {
2    public int threeSumSmaller(int[] nums, int target) {
3        // Sort the array to enable two-pointer technique
4        Arrays.sort(nums);
5      
6        int count = 0;
7        int n = nums.length;
8      
9        // Fix the first element and find valid pairs for the remaining two elements
10        for (int i = 0; i < n - 2; i++) {
11            int left = i + 1;
12            int right = n - 1;
13          
14            // Use two pointers to find all valid pairs
15            while (left < right) {
16                int sum = nums[i] + nums[left] + nums[right];
17              
18                if (sum < target) {
19                    // If current sum is less than target, all elements between left and right
20                    // form valid triplets with nums[i] and nums[left]
21                    // This is because array is sorted, so nums[left] + nums[right-1], 
22                    // nums[left] + nums[right-2], ..., nums[left] + nums[left+1] 
23                    // will all produce sums less than target
24                    count += right - left;
25                    left++;
26                } else {
27                    // If sum is greater than or equal to target, 
28                    // decrease the sum by moving right pointer leftward
29                    right--;
30                }
31            }
32        }
33      
34        return count;
35    }
36}
37
1class Solution {
2public:
3    int threeSumSmaller(vector<int>& nums, int target) {
4        // Sort the array to enable two-pointer technique
5        ranges::sort(nums);
6      
7        int count = 0;
8        int n = nums.size();
9      
10        // Fix the first element and find pairs for the remaining elements
11        for (int i = 0; i + 2 < n; ++i) {
12            // Initialize two pointers for the remaining array
13            int left = i + 1;
14            int right = n - 1;
15          
16            // Use two-pointer technique to find valid pairs
17            while (left < right) {
18                int sum = nums[i] + nums[left] + nums[right];
19              
20                if (sum < target) {
21                    // If current sum is less than target, all elements between
22                    // left and right will also form valid triplets with nums[i] and nums[left]
23                    // because the array is sorted (nums[left] + nums[left+1...right] will all be < target)
24                    count += right - left;
25                  
26                    // Move left pointer to explore new combinations
27                    ++left;
28                } else {
29                    // If sum is >= target, we need a smaller sum
30                    // Move right pointer to decrease the sum
31                    --right;
32                }
33            }
34        }
35      
36        return count;
37    }
38};
39
1/**
2 * Counts the number of triplets in the array whose sum is less than the target
3 * @param nums - Array of numbers to find triplets from
4 * @param target - The target sum that triplets should be less than
5 * @returns The count of valid triplets
6 */
7function threeSumSmaller(nums: number[], target: number): number {
8    // Sort the array in ascending order for two-pointer technique
9    nums.sort((a: number, b: number) => a - b);
10  
11    const arrayLength: number = nums.length;
12    let tripletCount: number = 0;
13  
14    // Fix the first element and use two pointers for the remaining elements
15    for (let firstIndex: number = 0; firstIndex < arrayLength - 2; firstIndex++) {
16        let leftPointer: number = firstIndex + 1;
17        let rightPointer: number = arrayLength - 1;
18      
19        // Two-pointer approach to find valid pairs
20        while (leftPointer < rightPointer) {
21            const currentSum: number = nums[firstIndex] + nums[leftPointer] + nums[rightPointer];
22          
23            if (currentSum < target) {
24                // All elements between leftPointer and rightPointer form valid triplets
25                // with nums[firstIndex] and nums[leftPointer]
26                tripletCount += rightPointer - leftPointer;
27                leftPointer++;
28            } else {
29                // Sum is too large, decrease it by moving right pointer left
30                rightPointer--;
31            }
32        }
33    }
34  
35    return tripletCount;
36}
37

Time and Space Complexity

Time Complexity: O(n^2)

The algorithm first sorts the array, which takes O(n log n) time. Then it uses a three-pointer approach:

  • The outer loop runs n-2 times, iterating through each possible first element
  • For each iteration of the outer loop, the two-pointer technique (with pointers j and k) traverses the remaining array at most once, taking O(n) time
  • Therefore, the nested loop structure gives us O(n) * O(n) = O(n^2) time
  • The overall time complexity is O(n log n) + O(n^2) = O(n^2), as O(n^2) dominates

Space Complexity: O(log n)

The space complexity comes from the sorting algorithm. Most efficient sorting algorithms like Python's Timsort (used in sort()) require O(log n) space for the recursion stack or auxiliary space during the sorting process. The algorithm itself only uses a constant amount of extra variables (ans, i, j, k, x), which is O(1). Therefore, the total space complexity is O(log n).

Common Pitfalls

Pitfall 1: Forgetting to Sort the Array

Issue: The two-pointer technique relies on the array being sorted. Without sorting, the algorithm's logic breaks down because we can't make assumptions about elements between the two pointers.

Wrong approach:

def threeSumSmaller(self, nums: List[int], target: int) -> int:
    # Missing nums.sort() - WRONG!
    count = 0
    n = len(nums)
    for i in range(n - 2):
        left = i + 1
        right = n - 1
        while left < right:
            # This logic doesn't work on unsorted arrays
            current_sum = nums[i] + nums[left] + nums[right]
            if current_sum < target:
                count += right - left  # Incorrect count for unsorted array
                left += 1
            else:
                right -= 1
    return count

Solution: Always sort the array first before applying the two-pointer technique.

Pitfall 2: Incorrect Counting Logic

Issue: When nums[i] + nums[j] + nums[k] < target, beginners often count only 1 triplet instead of k - j triplets.

Wrong approach:

if current_sum < target:
    count += 1  # WRONG! This only counts one triplet
    left += 1

Why it's wrong: When the sum is less than target with a sorted array, all elements between left and right indices form valid triplets with nums[i] and nums[left]. For example, if nums[i] + nums[left] + nums[right] < target, then:

  • (i, left, right) is valid
  • (i, left, right-1) is also valid (smaller sum)
  • (i, left, right-2) is also valid
  • ... and so on until (i, left, left+1)

Solution: Count all valid triplets at once with count += right - left.

Pitfall 3: Edge Case Handling - Array Too Small

Issue: Not checking if the array has at least 3 elements before processing.

Wrong approach:

def threeSumSmaller(self, nums: List[int], target: int) -> int:
    nums.sort()
    count = 0
    n = len(nums)
    # What if n < 3? The loop still runs!
    for i in range(n - 2):  # This could be range(-1) if n = 1
        ...

Solution: Add an early return for arrays with fewer than 3 elements:

def threeSumSmaller(self, nums: List[int], target: int) -> int:
    if len(nums) < 3:
        return 0
    nums.sort()
    # ... rest of the code

Pitfall 4: Pointer Movement Logic Error

Issue: Moving both pointers simultaneously or using wrong conditions for pointer movement.

Wrong approach:

while left < right:
    current_sum = nums[i] + nums[left] + nums[right]
    if current_sum < target:
        count += right - left
        left += 1
        right -= 1  # WRONG! Don't move both pointers

Why it's wrong: When we find a valid sum, we should only move the left pointer to explore new combinations with larger sums. Moving both pointers might skip valid triplets.

Solution: Move only one pointer at a time based on the comparison with target:

  • If sum < target: move left pointer right (to increase sum)
  • If sum >= target: move right pointer left (to decrease sum)
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of the following array represent a max heap?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More