Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Count index triplets (i < j < k) whose values form a geometric progression with common ratio `r`: `arr[j] = arr[i]·r` and `arr[k] = arr[j]·r`. Treat each element as the **middle** of a triplet and keep two frequency maps — `left` (counts of elements already passed) and `right` (counts of elements still ahead). For middle value `a`, the answer contribution is `left[a/r] · right[a·r]`. Accumulate into a **long** — the count can exceed `int` range (e.g. all-equal arrays with `r = 1`).
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you reframe a triple-nested search as 'fix the middle, count partners on each side', and whether you anticipate integer overflow on the count.
Where candidates slip
Forgetting `a % r == 0` (counting bogus predecessors), counting the current element on both sides because you didn't decrement `right` first, or using `int` and overflowing on all-equal inputs.
How to stand out
Explain why the middle element is the right pivot (it uniquely determines both partners as `a/r` and `a·r`), and call out the overflow before the interviewer does — switch the accumulator to `long`.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewElement a as MIDDLE terma % r == 0 ?NoYesExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Brute Force — Triple Nested Scan
1Hint 1Explore hintHide hint
A triplet needs three increasing indices i < j < k. What's the most direct way to examine every such triple?
2Hint 2Explore hintHide hint
For each (i, j, k) check the two ratio conditions: arr[j] == arr[i]·r and arr[k] == arr[j]·r.
3Hint 3Explore hintHide hint
Three nested loops, increment a counter on every match. Correct, but O(n³).
A · Core idea
The most literal reading: enumerate every ordered triple of indices i < j < k and test whether the values form a geometric progression with ratio r.
Why three loops with j > i and k > j. The triplet is ordered by position, so the inner loops must start past the outer index to avoid reusing a position and to avoid counting the same index set in a different order. That guarantees each index triple is examined exactly once.
Why multiplication, not addition, in the test. We compare arr[j] == arr[i]·r. Working with the product (rather than dividing) sidesteps integer-division pitfalls when a value isn't divisible by r.
Why we don't ship it. C(n,3) for n = 10⁵ is ~1.6×10¹⁴ iterations — utterly infeasible. It only demonstrates the definition; the frequency-map version is the real answer.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
arr = [1, 2, 2, 4], r = 2Array state
i=0,j=1,k=3
1,2,4 form a GP → count
2C · Code with teaching comments
Reference implementation previewpublic long countTriplets(List<Long> arr, long r) { int n = arr.size(); long count = 0; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) {Explore the full codeHide the full code
D · Why it works and complexityO(n³) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
The brute force makes the contract explicit: a triplet is an ordered (i, j, k) with two chained ratio equalities. Holding that picture lets you see the next step — the middle index j fully determines what the other two values must be.
ComplexityO(n³) time, O(1) space — Choosing 3 of n ordered indices is C(n,3) ≈ n³/6 triples, each checked in O(1).
E · Edge cases and pitfalls5arr = [1, 1, 1], r = 1 — Single triplet of equal values → 1.Explore edge casesHide edge cases
Edge cases handled
arr = [1, 1, 1], r = 1Single triplet of equal values → 1.arr = [1, 2, 4], r = 3No value equals its predecessor·3 → 0.
Pitfalls
- Using int for count — overflows on large all-equal arrays even though the loop logic is correct.
- Dividing instead of multiplying in the ratio test — breaks when a value isn't divisible by r.
- Shipping O(n³) on n = 10⁵ — it will time out badly.
Approach 2 of 2 · Optimal
Two Frequency Maps — Fix the Middle (Optimal)
1Hint 1Explore hintHide hint
If you fix the middle element of value a, what must the first and third values be? Exactly a/r and a·r.
2Hint 2Explore hintHide hint
Keep two maps: left counts of elements already seen, right counts of elements not yet processed. As you advance, move the current element from right to left.
3Hint 3Explore hintHide hint
For middle a, the number of triplets centered here is left[a/r] · right[a·r] — every valid left pairs with every valid right.
A · Core idea
The key reframe: stop enumerating triples and instead fix the middle element. If the middle value is a, the geometric progression forces the first value to be a/r and the third to be a·r. Those are uniquely determined, so the only question is how many of each exist on the correct side.
Two maps, left and right. right starts as the frequency of the whole array; left starts empty. As we walk left-to-right and reach element a, we first decrement `right[a]` — a is the middle now, no longer an available right-partner. Then everything still in right is strictly to the right of the current index, and everything in left is strictly to the left. That positional invariant is what enforces i < j < k.
Why we multiply. For middle a, there are c1 = left[a/r] valid first elements and c2 = right[a·r] valid third elements, each completely independent. Combining them is a Cartesian product, so the contribution is c1 · c2, summed over all middles.
The divisibility guard. a/r must be an integer to be a real array value. We only read left[a/r] when a % r == 0; otherwise c1 = 0. Skipping this guard would look up a fractional or truncated key and overcount.
Overflow. The grand total is a sum of products that can reach ~n³/6 (all equal, r = 1). That overflows 32-bit ints, so the accumulator must be long in Java (Python ints are unbounded, but the same care matters when porting).
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
arr = [1, 2, 2, 4], r = 2Hash-map state
a=2 (index 1)
4Match found
c1=left[1]=1, c2=right[4]=1 → +1
2C · Code with teaching comments
D · Why it works and complexityO(n) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
Fixing the middle term is the unlock: it converts a search over triples into two independent counting questions answered in O(1) by hash maps. The same 'pivot on the center, multiply the sides' trick appears in counting arithmetic triplets and palindromic/symmetric subsequence counts.
ComplexityO(n) time, O(n) space — One pass; each step does O(1) map operations. Both maps hold at most n distinct keys → O(n) time and space.
E · Edge cases and pitfalls5arr = [1,1,1,1], r = 1 — Every choice of 3 indices counts → C(4,3) = 4. Demonstrates why long is required at scale.Explore edge casesHide edge cases
Edge cases handled
arr = [1,1,1,1], r = 1Every choice of 3 indices counts → C(4,3) = 4. Demonstrates why long is required at scale.arr = [1, 2, 4], r = 2Single GP → 1, with right correctly emptied as we advance.
Pitfalls
- Skipping the `a % r == 0` guard — reads a wrong key and overcounts.
- Decrementing right after the lookup — the current element wrongly counts as its own partner.
- Using int for the accumulator — overflows on all-equal arrays with r = 1.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic long countTriplets(List<Long> arr, long r) { Map<Long, Long> left = new HashMap<>(); Map<Long, Long> right = new HashMap<>(); for (long a : arr) { right.merge(a, 1L, Long::sum);Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Integer overflow on the count
With arr all equal and r = 1, the answer is C(n,3) ≈ n³/6 which exceeds 2³¹ for n in the thousands. An int silently wraps to a wrong (often negative) value.
Explore the code comparisonHide the code comparison
Integer overflow on the count
With arr all equal and r = 1, the answer is C(n,3) ≈ n³/6 which exceeds 2³¹ for n in the thousands. An int silently wraps to a wrong (often negative) value.
Don't write this
int count = 0;
// ... count += c1 * c2;Write this instead
long count = 0;
// ... count += c1 * c2;2Missing the divisibility guard
If a is not divisible by r, a/r truncates (Java) or is a fraction (math), so left[a/r] points at the wrong key and overcounts.
Explore the code comparisonHide the code comparison
Missing the divisibility guard
If a is not divisible by r, a/r truncates (Java) or is a fraction (math), so left[a/r] points at the wrong key and overcounts.
Don't write this
c1 = left[a // r]
c2 = right[a * r]Write this instead
c1 = left[a // r] if a % r == 0 else 0
c2 = right[a * r]3Decrement order on the right map
If you read right[a·r] before removing the current a, an element can be counted as its own successor when r = 1.
Explore the code comparisonHide the code comparison
Decrement order on the right map
If you read right[a·r] before removing the current a, an element can be counted as its own successor when r = 1.
Don't write this
c2 = right[a * r]
right[a] -= 1Write this instead
right[a] -= 1
c2 = right[a * r]Revise
Carry the pattern into the interview
Pattern to remember
Fix the middle element + two frequency maps (left/right)
Code pattern to memorize
right = freq(arr) # everything starts on the right
left = {}
for a in arr:
right[a] -= 1 # a leaves the right side
c1 = left[a // r] if a % r == 0 else 0
c2 = right[a * r]
count += c1 * c2 # use long
left[a] += 1 # a joins the left sideRules to remember
- 1Iterate with each element as the middle of the GP — then the predecessor is
a/r(to the left) and the successor isa·r(to the right). - 2Decrement
right[a]before the lookup so the current element is never counted as its own left or right partner. - 3Guard the predecessor with
a % r == 0— ifaisn't divisible byr, no integera/rexists andleft[a/r]is meaningless. - 4Use long for the running count: with
arr = [1,1,…,1]andr = 1the answer is C(n,3) ≈ n³/6, which overflows a 32-bit int well before n = 10⁵. - 5Multiply the two counts (
c1 · c2) — every left predecessor pairs with every right successor, so it's a product, not a sum.
Use this pattern when
- Counting triplets/tuples in a fixed multiplicative or additive relation
- You can fix the **middle** element and independently count valid left and right partners
- Order matters (i < j < k) and you need counts, not the triplets themselves
- Values can repeat, so you need frequencies rather than a seen/not-seen set
Choose another pattern when
- You must return the actual triplets, not a count → enumeration, not frequency products
- The relation is additive over a sorted array (3Sum) → sort + two pointers fits better
- No repeated values and tiny n → the O(n²)/O(n³) brute force is fine and simpler
- Ratio can be fractional and values aren't integers → division/divisibility guard breaks down
Interview takeaway
When you must count ordered triplets in a relation, fix the middle element and ask two independent questions — 'how many valid lefts?' and 'how many valid rights?' — then multiply. Two frequency maps answer both in O(1), turning O(n³) into O(n).
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternFix-the-middle counting with twin frequency maps. When ordered triplets obey a relation that uniquely determines the outer terms from the middle, count valid lefts and rights independently and multiply — O(n) instead of O(n³). Mind the overflow on the count.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Count Pairs with given sumSame frequency-map idea but for additive pairs instead of multiplicative triplets.
- 3SumComing soonAdditive triplets over a sorted array — sort then two pointers.
- Arithmetic TripletsComing soonFix the middle and look for value − d and value + d in a set.