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:
- The indices must follow the order:
0 <= i < j < k < n
- 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:
- Fix the first element
nums[i]
by iterating through the array - For each fixed
i
, use two pointersj
andk
wherej
starts right afteri
andk
starts at the end of the array - When
nums[i] + nums[j] + nums[k] < target
, all elements betweenj
andk
would also form valid triplets withi
andj
(since the array is sorted). This gives usk - j
valid triplets at once - Move the pointers accordingly: increment
j
when the sum is less than target, decrementk
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.
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 ati + 1
(the next element afteri
)k
starts atn - 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 indicesj+1
andk
would also form valid triplets withi
andj
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
- The array is sorted, so
- If
x >= target
: The current sum is too large, so we need to decrease it by movingk
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 EvaluatorExample 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
- Sum is not less than target, move
- Now
j = 2
,k = 2
, soj >= 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
- Sum is too large, move
- Now
j = 2
,k = 2
, soj >= 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:
- Indices (0, 1, 2):
nums[0] + nums[1] + nums[2] = -2 + 0 + 1 = -1 < 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
andk
) traverses the remaining array at most once, takingO(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)
, asO(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)
Which of the following array represent a max heap?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Want a Structured Path to Master System Design Too? Don’t Miss This!