3583. Count Special Triplets
Problem Description
You are given an integer array nums
.
A special triplet is defined as a triplet of indices (i, j, k)
such that:
0 <= i < j < k < n
, wheren = nums.length
nums[i] == nums[j] * 2
nums[k] == nums[j] * 2
Return the total number of special triplets in the array.
Since the answer may be large, return it modulo 10^9 + 7
.
Intuition
The task is to count triplets (i, j, k)
where nums[i]
and nums[k]
are both exactly double nums[j]
, with the indices in order (i < j < k
). Instead of checking all possible triplets, which would be slow for large arrays, notice that for each possible "middle" element nums[j]
, we only care about how many times nums[j] * 2
appears both before and after index j
.
If we can quickly count those occurrences, then for each j
, the total number of triplets with j
as the middle is just the product of those two counts. This can be done efficiently by keeping track of counts to the left and right of j
as we iterate through the array. Using hash tables (or Counters) to store these counts lets us compute the answer in one pass, updating our counts as we go.
Solution Approach
The solution uses an efficient way to count the valid triplets by focusing on each possible "middle" index j
and using hash tables to keep track of occurrences:
- Use two hash tables,
left
andright
, where:left[x]
tracks how many times numberx
has appeared before the current index.right[x]
tracks how many times numberx
will appear after the current index.
- Initially, count all occurrences of each number in
nums
and store them inright
. Theleft
hash table starts empty. - Iterate over the array from left to right using index
j
as the "middle" of the triplet:- For each
nums[j]
, decrement its count inright
since it's now being considered. - To form a valid triplet
(i, j, k)
, need:i < j
wherenums[i] == nums[j] * 2
(count inleft[nums[j] * 2]
)k > j
wherenums[k] == nums[j] * 2
(count inright[nums[j] * 2]
)
- Multiply the counts for
left[nums[j] * 2]
andright[nums[j] * 2]
to get all ways to choosei
andk
for givenj
, and add to the answer. - After processing, increment the count of
nums[j]
inleft
.
- For each
- Since the result can be large, keep the answer modulo
10^9 + 7
.
Here is the code that implements this logic:
from collections import Counter
class Solution:
def specialTriplets(self, nums: List[int]) -> int:
left = Counter()
right = Counter(nums)
ans = 0
mod = 10**9 + 7
for x in nums:
right[x] -= 1
ans = (ans + left[x * 2] * right[x * 2] % mod) % mod
left[x] += 1
return ans
- The algorithm runs in linear time, iterating through
nums
once and using constant work per position with hash table lookups and updates. This makes it very efficient for large arrays.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's illustrate the solution with a small array:
nums = [4, 2, 4, 8, 2]
We want to count the number of special triplets (i, j, k)
such that:
0 <= i < j < k < 5
nums[i] == nums[j] * 2
nums[k] == nums[j] * 2
Step-by-step
1. Initialization
left = Counter()
(Initially empty)right = Counter({4: 2, 2: 2, 8: 1})
(Counts of all elements)ans = 0
2. Iterate through each j
(the middle index):
j = 0, nums[0] = 4
- Decrement
right[4]
→right[4] = 1
- Count of
nums[j]*2
inleft
=left[8] = 0
- Count of
nums[j]*2
inright
=right[8] = 1
- Triplets for this j:
0 * 1 = 0
(add to ans) - Increment
left[4]
→left[4] = 1
j = 1, nums[1] = 2
- Decrement
right[2]
→right[2] = 1
nums[j]*2
= 4left[4] = 1
,right[4] = 1
- Triplets:
1 * 1 = 1
(add to ans, now ans = 1) - Increment
left[2]
→left[2] = 1
j = 2, nums[2] = 4
- Decrement
right[4]
→right[4] = 0
nums[j]*2
= 8left[8] = 0
,right[8] = 1
- Triplets:
0 * 1 = 0
- Increment
left[4]
→left[4] = 2
j = 3, nums[3] = 8
- Decrement
right[8]
→right[8] = 0
nums[j]*2
= 16left[16] = 0
,right[16] = 0
- Triplets:
0 * 0 = 0
- Increment
left[8]
→left[8] = 1
j = 4, nums[4] = 2
- Decrement
right[2]
→right[2] = 0
nums[j]*2
= 4left[4]=2
,right[4]=0
- Triplets:
2 * 0 = 0
- Increment
left[2]
→left[2] = 2
Final count
The total number of special triplets in [4, 2, 4, 8, 2]
is 1.
This process efficiently counts triplets by only tracking how many valid i
(before j
) and k
(after j
) can pair up with each choice of j
, avoiding slow triple-nested loops.
Solution Implementation
1from collections import Counter
2from typing import List
3
4class Solution:
5 def specialTriplets(self, nums: List[int]) -> int:
6 # Counter to keep track of numbers to the left of current element
7 left = Counter()
8 # Counter to keep track of numbers to the right of current element
9 right = Counter(nums)
10 ans = 0
11 mod = 10**9 + 7 # Large prime for modulo
12
13 # Iterate through each number in nums
14 for x in nums:
15 # Decrement current number from right as we're processing it now
16 right[x] -= 1
17
18 # Count combinations where both in left and right
19 # There is a number equal to 2 * x
20 count_left = left[2 * x]
21 count_right = right[2 * x]
22 ans = (ans + count_left * count_right % mod) % mod
23
24 # Add current number to the left counter
25 left[x] += 1
26
27 return ans
28
1class Solution {
2 public int specialTriplets(int[] nums) {
3 // Frequency count of elements that appear to the left of the current index.
4 Map<Integer, Integer> leftCounts = new HashMap<>();
5 // Frequency count of elements that appear to the right of the current index.
6 Map<Integer, Integer> rightCounts = new HashMap<>();
7
8 // Initialize rightCounts with the frequency of each element in nums.
9 for (int num : nums) {
10 rightCounts.merge(num, 1, Integer::sum);
11 }
12
13 long countTriplets = 0;
14 final int MOD = 1_000_000_007;
15
16 // Iterate through each element, treating it as the "middle" of the triplet.
17 for (int num : nums) {
18 // Move num from rightCounts to leftCounts
19 rightCounts.merge(num, -1, Integer::sum);
20
21 // Number we're looking for is 2 * num
22 int target = num * 2;
23
24 // Count pairs where left and right both have 'target'
25 int leftFrequency = leftCounts.getOrDefault(target, 0);
26 int rightFrequency = rightCounts.getOrDefault(target, 0);
27
28 // Update the answer with the combinations of matching targets on both sides
29 countTriplets = (countTriplets + 1L * leftFrequency * rightFrequency % MOD) % MOD;
30
31 // Update leftCounts for next iteration
32 leftCounts.merge(num, 1, Integer::sum);
33 }
34
35 return (int) countTriplets;
36 }
37}
38
1class Solution {
2public:
3 int specialTriplets(vector<int>& nums) {
4 // Maps to count occurrences of values to the left and right of current position
5 unordered_map<int, int> left, right;
6
7 // Initialize right map with counts of all numbers in nums
8 for (int num : nums) {
9 right[num]++;
10 }
11
12 long long ans = 0;
13 const int mod = 1e9 + 7;
14
15 // Iterate through nums, treating each number as the 'middle' of the triplet
16 for (int num : nums) {
17 // Move current num from 'right' to 'left' (i.e., shift window)
18 right[num]--;
19
20 // For current num, count the pairs where both left and right have (num * 2)
21 // This counts (i, j, k) such that nums[i] == nums[k] == 2 * nums[j] and i < j < k
22 ans = (ans + 1LL * left[num * 2] * right[num * 2] % mod) % mod;
23
24 // Increment count of current num in 'left'
25 left[num]++;
26 }
27
28 return static_cast<int>(ans);
29 }
30};
31
1/**
2 * Counts the number of special triplets in the array.
3 * A triplet (i, j, k) is special if nums[j] == 2 * nums[i] and nums[k] == 2 * nums[j].
4 * @param nums Array of integers
5 * @returns The number of special triplets modulo 1e9 + 7
6 */
7function specialTriplets(nums: number[]): number {
8 // Maps to store the frequency of numbers on the left and right of the current index
9 const leftCount = new Map<number, number>();
10 const rightCount = new Map<number, number>();
11
12 // Populate rightCount map with frequency of each number
13 for (const num of nums) {
14 rightCount.set(num, (rightCount.get(num) || 0) + 1);
15 }
16
17 let tripletCount = 0;
18 const MOD = 1e9 + 7;
19
20 // Iterate through each number in nums as the middle element of the triplet
21 for (const num of nums) {
22 // Decrement the current number from the right since it's now being considered
23 rightCount.set(num, (rightCount.get(num) || 0) - 1);
24
25 // Count of candidates for left side (nums[i] == num / 2)
26 const leftCandidates = leftCount.get(num * 2) || 0;
27
28 // Count of candidates for right side (nums[k] == num * 2)
29 const rightCandidates = rightCount.get(num * 2) || 0;
30
31 // Add product to the result, modulo MOD
32 tripletCount = (tripletCount + ((leftCandidates * rightCandidates) % MOD)) % MOD;
33
34 // Increment the left side map for future iterations
35 leftCount.set(num, (leftCount.get(num) || 0) + 1);
36 }
37
38 return tripletCount;
39}
40
Time and Space Complexity
The time complexity is O(n)
, and the space complexity is O(n)
, where n
is the length of the array nums
.
What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)
Recommended Readings
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!