923. 3Sum With Multiplicity
Problem Description
You are given an integer array arr
and an integer target
. Your task is to find the number of triplets (i, j, k)
where the indices satisfy i < j < k
and the sum arr[i] + arr[j] + arr[k]
equals the target
.
Since the count of such triplets can be very large, you need to return the result modulo 10^9 + 7
.
The solution uses a counting approach with enumeration. It maintains a counter cnt
to track the frequency of each element in the array. For each middle element arr[j]
, it first decrements the count of that element, then looks at all elements before position j
as potential first elements arr[i]
. For each pair (arr[i], arr[j])
, it calculates what the third element c
should be (c = target - arr[i] - arr[j]
) and adds the count of c
in the remaining elements to the answer. This ensures we only count valid triplets where i < j < k
.
Intuition
The key insight is that we need to count triplets with a specific ordering constraint i < j < k
. A naive approach would check all possible triplets, but that would be O(nยณ)
which is too slow.
Instead, we can fix the middle element and think about the problem differently. If we fix the middle element arr[j]
, we need to find pairs where one element comes before j
and one comes after j
that sum up to target - arr[j]
.
The clever part is using a frequency counter. We start with a counter containing all elements. As we iterate through the array and fix each element as the middle element arr[j]
, we first remove it from the counter (decrement its count). This ensures that when we look for the third element, we're only considering elements that come after position j
.
For each element arr[i]
that comes before j
, we know exactly what the third element needs to be: c = target - arr[i] - arr[j]
. The counter tells us how many such elements exist after position j
. By adding cnt[c]
to our answer, we're counting all valid triplets with arr[i]
as the first element, arr[j]
as the middle element, and any occurrence of c
after j
as the third element.
This approach reduces the complexity to O(nยฒ)
because for each of the n
middle positions, we check all previous elements, and the counter lookup is O(1)
.
Learn more about Two Pointers and Sorting patterns.
Solution Approach
The implementation uses a counting and enumeration strategy with the following steps:
-
Initialize the counter: Create a
Counter
objectcnt
that stores the frequency of each element in the arrayarr
. Since the problem constraints limit values to the range[0, 100]
, we could also use an array of size 101. -
Set up the modulo value: Define
mod = 10^9 + 7
for the final result calculation. -
Enumerate the middle element: Iterate through the array with index
j
and valueb = arr[j]
. This element will serve as the middle element of our triplet. -
Update the counter: Before looking for matching pairs, decrement
cnt[b]
by 1. This crucial step ensures that when we search for the third element, we only count elements that appear after positionj
. -
Enumerate the first element: For each middle element
arr[j]
, iterate through all elements before it (arr[:j]
). Each of these elementsa = arr[i]
can potentially be the first element of a valid triplet. -
Calculate the required third element: For each pair
(a, b)
, calculate what the third element must be:c = target - a - b
. -
Count valid triplets: Add
cnt[c]
to the answer. This represents the number of times valuec
appears after positionj
, giving us all valid triplets with first elementa
, middle elementb
, and third elementc
. -
Apply modulo operation: After each addition, apply the modulo operation
ans = (ans + cnt[c]) % mod
to prevent integer overflow. -
Return the result: After processing all possible middle elements and their corresponding pairs, return the final answer.
The time complexity is O(nยฒ)
where n
is the length of the array, as we have nested loops iterating through the array. The space complexity is O(1)
if we consider the counter size as constant (since values are limited to range [0, 100]
), or O(n)
if we consider the general case.
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 a concrete example with arr = [1, 1, 2, 2, 3]
and target = 6
.
Initial Setup:
- Create counter:
cnt = {1: 2, 2: 2, 3: 1}
- Initialize
ans = 0
Iteration 1: j=0, arr[j]=1 (first middle element)
- Decrement counter:
cnt = {1: 1, 2: 2, 3: 1}
- No elements before j=0, so no pairs to check
- Move to next iteration
Iteration 2: j=1, arr[j]=1 (second middle element)
- Decrement counter:
cnt = {1: 0, 2: 2, 3: 1}
- Check element before j=1:
arr[0]=1
- Calculate needed third element:
c = 6 - 1 - 1 = 4
- Check counter:
cnt[4] = 0
- Add to answer:
ans = 0 + 0 = 0
- Calculate needed third element:
Iteration 3: j=2, arr[j]=2 (third middle element)
- Decrement counter:
cnt = {1: 0, 2: 1, 3: 1}
- Check elements before j=2:
- For
arr[0]=1
:c = 6 - 1 - 2 = 3
,cnt[3] = 1
,ans = 0 + 1 = 1
- For
arr[1]=1
:c = 6 - 1 - 2 = 3
,cnt[3] = 1
,ans = 1 + 1 = 2
- For
Iteration 4: j=3, arr[j]=2 (fourth middle element)
- Decrement counter:
cnt = {1: 0, 2: 0, 3: 1}
- Check elements before j=3:
- For
arr[0]=1
:c = 6 - 1 - 2 = 3
,cnt[3] = 1
,ans = 2 + 1 = 3
- For
arr[1]=1
:c = 6 - 1 - 2 = 3
,cnt[3] = 1
,ans = 3 + 1 = 4
- For
arr[2]=2
:c = 6 - 2 - 2 = 2
,cnt[2] = 0
,ans = 4 + 0 = 4
- For
Iteration 5: j=4, arr[j]=3 (fifth middle element)
- Decrement counter:
cnt = {1: 0, 2: 0, 3: 0}
- Check elements before j=4:
- For
arr[0]=1
:c = 6 - 1 - 3 = 2
,cnt[2] = 0
,ans = 4 + 0 = 4
- For
arr[1]=1
:c = 6 - 1 - 3 = 2
,cnt[2] = 0
,ans = 4 + 0 = 4
- For
arr[2]=2
:c = 6 - 2 - 3 = 1
,cnt[1] = 0
,ans = 4 + 0 = 4
- For
arr[3]=2
:c = 6 - 2 - 3 = 1
,cnt[1] = 0
,ans = 4 + 0 = 4
- For
Final Result: The function returns 4.
The valid triplets found are:
- (0, 2, 4): arr[0]=1, arr[2]=2, arr[4]=3 โ 1+2+3=6
- (1, 2, 4): arr[1]=1, arr[2]=2, arr[4]=3 โ 1+2+3=6
- (0, 3, 4): arr[0]=1, arr[3]=2, arr[4]=3 โ 1+2+3=6
- (1, 3, 4): arr[1]=1, arr[3]=2, arr[4]=3 โ 1+2+3=6
Solution Implementation
1from typing import List
2from collections import Counter
3
4class Solution:
5 def threeSumMulti(self, arr: List[int], target: int) -> int:
6 """
7 Count the number of triplets (i, j, k) where i < j < k
8 and arr[i] + arr[j] + arr[k] == target.
9
10 Args:
11 arr: List of integers
12 target: Target sum for triplets
13
14 Returns:
15 Number of valid triplets modulo 10^9 + 7
16 """
17 # Define modulo constant for large number handling
18 MOD = 10**9 + 7
19
20 # Count frequency of each element in the array
21 frequency_map = Counter(arr)
22
23 # Initialize result counter
24 triplet_count = 0
25
26 # Iterate through middle element position
27 for middle_idx, middle_val in enumerate(arr):
28 # Decrement count of middle element to avoid reusing it
29 frequency_map[middle_val] -= 1
30
31 # Check all elements before middle position as first element
32 for left_val in arr[:middle_idx]:
33 # Calculate required third element value
34 required_third = target - left_val - middle_val
35
36 # Add count of valid third elements (must be after middle_idx)
37 triplet_count = (triplet_count + frequency_map[required_third]) % MOD
38
39 return triplet_count
40
1class Solution {
2 public int threeSumMulti(int[] arr, int target) {
3 // Modulo value for the result as per problem requirements
4 final int MOD = (int) 1e9 + 7;
5
6 // Frequency array to count occurrences of each number (0-100 range)
7 int[] frequency = new int[101];
8
9 // Count the frequency of each element in the array
10 for (int num : arr) {
11 ++frequency[num];
12 }
13
14 int arrayLength = arr.length;
15 int tripletCount = 0;
16
17 // Fix the middle element (j) of the triplet
18 for (int j = 0; j < arrayLength; ++j) {
19 // Temporarily remove arr[j] from frequency count to avoid using it twice
20 --frequency[arr[j]];
21
22 // Fix the first element (i) of the triplet, ensuring i < j
23 for (int i = 0; i < j; ++i) {
24 // Calculate the required third element to reach the target sum
25 int requiredThirdValue = target - arr[i] - arr[j];
26
27 // Check if the required value is within valid range and exists
28 if (requiredThirdValue >= 0 && requiredThirdValue < frequency.length) {
29 // Add the count of available third elements to form valid triplets
30 tripletCount = (tripletCount + frequency[requiredThirdValue]) % MOD;
31 }
32 }
33 }
34
35 return tripletCount;
36 }
37}
38
1class Solution {
2public:
3 int threeSumMulti(vector<int>& arr, int target) {
4 // Modulo value for the result as per problem requirements
5 const int MOD = 1e9 + 7;
6
7 // Frequency array to count occurrences of each number (0-100 range)
8 int frequency[101] = {0};
9
10 // Count the frequency of each element in the array
11 for (int num : arr) {
12 ++frequency[num];
13 }
14
15 int arraySize = arr.size();
16 int result = 0;
17
18 // Fix the middle element (j) of the triplet
19 for (int j = 0; j < arraySize; ++j) {
20 // Decrement frequency of current middle element to avoid counting it
21 // when looking for the third element
22 --frequency[arr[j]];
23
24 // Iterate through all possible first elements (i < j)
25 for (int i = 0; i < j; ++i) {
26 // Calculate the required third element value
27 int requiredThirdValue = target - arr[i] - arr[j];
28
29 // Check if the third value is within valid range
30 if (requiredThirdValue >= 0 && requiredThirdValue <= 100) {
31 // Add the count of available third elements to result
32 // All elements after j with value = requiredThirdValue are valid
33 result = (result + frequency[requiredThirdValue]) % MOD;
34 }
35 }
36 }
37
38 return result;
39 }
40};
41
1/**
2 * Counts the number of tuples (i, j, k) such that i < j < k and arr[i] + arr[j] + arr[k] == target
3 * @param arr - Array of integers (values between 0 and 100)
4 * @param target - Target sum to find
5 * @returns Number of valid triplets modulo 10^9 + 7
6 */
7function threeSumMulti(arr: number[], target: number): number {
8 // Modulo value to prevent integer overflow
9 const MOD: number = 10 ** 9 + 7;
10
11 // Frequency counter array for values 0-100
12 const frequencyCount: number[] = Array(101).fill(0);
13
14 // Count frequency of each element in the array
15 for (const value of arr) {
16 frequencyCount[value]++;
17 }
18
19 // Initialize result counter
20 let result: number = 0;
21 const arrayLength: number = arr.length;
22
23 // Iterate through array positions for the third element (k position)
24 for (let k = 0; k < arrayLength; k++) {
25 // Decrement count for current element as it's being used as arr[k]
26 frequencyCount[arr[k]]--;
27
28 // Iterate through all possible first elements (i position) before k
29 for (let i = 0; i < k; i++) {
30 // Calculate required value for the second element
31 const requiredValue: number = target - arr[i] - arr[k];
32
33 // Check if required value is valid and within bounds
34 if (requiredValue >= 0 && requiredValue < frequencyCount.length) {
35 // Add count of available elements with required value
36 result = (result + frequencyCount[requiredValue]) % MOD;
37 }
38 }
39 }
40
41 return result;
42}
43
Time and Space Complexity
The time complexity is O(nยฒ)
, where n
is the length of the array arr
. This is because:
- The outer loop iterates through each element in
arr
, runningn
times - For each iteration
j
, the inner loop iterates through all elements before indexj
, which ranges from 0 ton-1
elements - The total number of operations is
0 + 1 + 2 + ... + (n-1) = n(n-1)/2
, which simplifies toO(nยฒ)
- The Counter lookup
cnt[c]
and Counter updatecnt[b] -= 1
operations are bothO(1)
on average
The space complexity is O(n)
or alternatively O(C)
, where C
is the range of possible values in the array. This is because:
- The Counter
cnt
stores at mostn
unique elements from the array, requiringO(n)
space - Since the problem constraints limit element values to a range (typically 0 to 100 in this problem), the Counter can also be bounded by
O(C)
whereC = 100
- The space complexity can be expressed as
O(min(n, C))
, which in this specific problem context withC = 100
is effectivelyO(C)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Properly Managing the Counter State
One of the most critical pitfalls in this solution is forgetting to decrement the counter for the middle element. If you don't decrement frequency_map[middle_val]
, you might count invalid triplets where the same element position is used multiple times.
Incorrect approach:
for middle_idx, middle_val in enumerate(arr):
# WRONG: Not decrementing the counter
for left_val in arr[:middle_idx]:
required_third = target - left_val - middle_val
triplet_count += frequency_map[required_third]
Why it fails: This would count cases where the middle element itself could be counted as the third element, violating the i < j < k
constraint.
2. Integer Overflow Issues
Without applying the modulo operation during accumulation, the result can overflow for large inputs.
Incorrect approach:
# Accumulate without modulo triplet_count += frequency_map[required_third] # Apply modulo only at the end return triplet_count % MOD
Correct approach:
# Apply modulo during each addition triplet_count = (triplet_count + frequency_map[required_third]) % MOD
3. Incorrect Index Ordering
A common mistake is not maintaining the strict ordering i < j < k
. Some might try to use combinations or permutations without considering the index constraints.
Incorrect approach:
# Using combinations without considering actual indices
from itertools import combinations
count = 0
for triplet in combinations(arr, 3):
if sum(triplet) == target:
count += 1
Why it fails: This counts unique value combinations, not index-based triplets. For example, [1,1,2]
with target 4 should count the triplet twice (indices 0,1,2 and indices 0,2,1 are different), but combinations would only count it once.
4. Not Handling Negative Values in Required Third Element
If the calculated required_third
is negative or out of the valid range, accessing frequency_map[required_third]
still works with Counter (returns 0), but with an array-based approach, it could cause issues.
Potential issue with array-based counting:
# If using array instead of Counter frequency_array = [0] * 101 # ... required_third = target - left_val - middle_val # This could be negative or > 100 triplet_count += frequency_array[required_third] # IndexError!
Solution:
# Add bounds checking for array approach if 0 <= required_third <= 100: triplet_count += frequency_array[required_third]
5. Reconstructing Counter Instead of Decrementing
Some might try to create a new counter for remaining elements instead of decrementing, which is inefficient.
Inefficient approach:
for middle_idx, middle_val in enumerate(arr):
# Creating new counter each time - O(n) operation
remaining_counter = Counter(arr[middle_idx + 1:])
for left_val in arr[:middle_idx]:
required_third = target - left_val - middle_val
triplet_count += remaining_counter[required_third]
Why the original approach is better: Decrementing and maintaining a single counter is more efficient (O(1) per update) than creating new counters (O(n) per iteration).
You are given an array of intervals where intervals[i] = [start_i, end_i]
represent the start and end of the ith
interval. You need to merge all overlapping intervals and return an array of the non-overlapping intervals that cover all the intervals in the input.
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
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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donโt Miss This!