Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Count pairs of substrings that are anagrams of each other. Two substrings are anagrams exactly when they share the same letter-frequency signature, so group every substring by its signature and, for each group of size k, add C(k,2) = k(k-1)/2 pairs. Reduce a brute-force O(n⁴) pair check to an O(n²) grouping pass with a hash map keyed by signature.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you recognise that anagram-equivalence becomes a hash-map grouping problem, and whether you count pairs with C(k,2) instead of off-by-one guesses like k or k².
Where candidates slip
Returning the group size k instead of C(k,2), or building the signature in a way that doesn't make anagrams collide (e.g. keying on the raw substring).
How to stand out
State the brute-force O(n⁴) baseline, then reduce it to grouping by a canonical signature, and derive C(k,2) explicitly as 'choose 2 of the k equal substrings'.
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 previewAll substringsBucket size kYesNoExplore 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 — Compare Every Substring Pair
1Hint 1Explore hintHide hint
There are O(n²) substrings. The most direct count is to test every unordered pair of them for the anagram property.
2Hint 2Explore hintHide hint
Two substrings are anagrams iff sorting their characters yields the same string, or their 26-letter counts match. Only compare equal-length substrings.
3Hint 3Explore hintHide hint
O(n²) substrings → O(n⁴) pairs, and each comparison costs up to O(n). That's far too slow in general, but it pins down exactly what 'anagram pair' means.
A · Core idea
The literal reading: enumerate every substring, then test each unordered pair to see whether one is an anagram of the other. Count the pairs that pass.
The anagram test. Two strings are anagrams iff they contain the same letters with the same multiplicities. You can check that by sorting both and comparing, or by comparing their 26-element frequency vectors. Either way, only equal-length substrings can possibly match, so you skip unequal lengths immediately.
Why it's correct. It checks exactly the definition against every candidate pair — nothing is missed, nothing double-counted as long as you only consider pairs with the first index strictly before the second.
Why we don't ship this. The substring count is O(n²), so the number of pairs is O(n⁴), and each comparison adds another factor. The redundancy is obvious: we compare the same substring against many others, re-deriving its signature every time. Computing each signature once and grouping by it removes the pairwise blow-up — that's the optimal approach.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
s = "abba"Array state
length 1
a, b, b, a → sorted unchanged
4C · Code with teaching comments
Reference implementation previewpublic int sherlockAndAnagrams(String s) { int n = s.length(); List<String> subs = new ArrayList<>(); 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(n) spaceOpen the invariant and proofExplore the proofHide the proof
Every pairwise comparison re-discovers the same fact: substrings sharing a signature are anagrams. Compute the signature once per substring and let a hash map do the matching — pairs that share a key are pairs that match, with no explicit comparison.
ComplexityO(n⁴) time, O(n) space — There are n(n+1)/2 = O(n²) substrings, hence O(n⁴) unordered pairs. Each anagram check (sort or count) costs O(n), so the full cost is O(n⁴) to O(n⁵) depending on the check. Space is O(n) for one comparison buffer.
E · Edge cases and pitfalls3Comparing substrings of different lengths — wasted work; they can never be anagrams.Explore edge casesHide edge cases
Pitfalls
- Comparing substrings of different lengths — wasted work; they can never be anagrams.
- Counting ordered pairs (b from 0) — doubles the result.
- Re-sorting the same substring repeatedly inside the pair loop — the source of the O(n⁴) cost.
Approach 2 of 2 · Optimal
Group by Signature — Hash Map (Optimal)
1Hint 1Explore hintHide hint
Instead of comparing pairs, give each substring a canonical key so anagrams land in the same bucket. What key makes all anagrams equal?
2Hint 2Explore hintHide hint
Use a hash map from signature → count. The signature can be the sorted characters or, faster, a 26-length letter-count tuple. Increment the count for each substring.
3Hint 3Explore hintHide hint
After bucketing, a bucket holding k substrings contributes C(k,2) = k(k-1)/2 pairs. Sum that over all buckets.
A · Core idea
Stop comparing pairs and start grouping. Walk every substring once, reduce it to a canonical signature, and tally how many substrings share each signature in a hash map. Anagrams — and only anagrams — collide on the same key, so each bucket is a set of mutually-anagram substrings.
Choosing the signature. Two good keys:
- Sorted characters:
"ba"and"ab"both become"ab". Simple, and length is baked in (different lengths sort to different-length strings, so they never collide). - 26-letter count tuple: record how many of each letter the substring has, e.g.
(a:1, b:1). Building it is a single O(length) scan, and it's the more scalable key for large alphabets.
Either makes every pair of anagrams map to the identical key without an explicit comparison.
Why C(k,2), and why not k. Suppose a bucket holds k substrings that are all mutual anagrams. Every unordered pair drawn from those k is one valid anagram pair, and the number of such pairs is the binomial coefficient C(k,2) = k(k-1)/2. A bucket of 4 (like the four length-1 ks in "kkkk") gives 4·3/2 = 6 pairs — not 4, and not 12. Summing C(k,2) across all buckets is the final answer. Mis-remembering this as k or k(k-1) is the most common scoring error on this problem.
Why this beats brute force. Brute force re-derives each substring's signature for every comparison; here each signature is computed once and the hash map does the matching implicitly. We trade the O(n⁴) pair scan for an O(n²) grouping pass (times the O(n) signature build), and trade a little memory for the map.
Why the count must include position. We never deduplicate identical substrings — the two as in "aa" are two distinct substrings and legitimately form one pair. The map counts occurrences, so positional distinctness is preserved automatically.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
s = "abba"Hash-map state
length-1 substrings
Continue scanning
a:2, b:2 → 1 + 1 pairs
4C · Code with teaching comments
D · Why it works and complexityO(n³) (O(n²) substrings × O(n) signature) timeO(n²) spaceOpen the invariant and proofExplore the proofHide the proof
Replacing pairwise comparison with 'canonical key + bucket + combinatorial count' is a workhorse pattern. The hash map turns 'are these two equivalent?' (asked O(n⁴) times) into 'how many share this key?' (answered once per bucket), and C(k,2) converts a bucket size into its pair count in O(1).
ComplexityO(n³) (O(n²) substrings × O(n) signature) time, O(n²) space — There are O(n²) substrings; building each signature costs O(n) (scan its characters or sort), so populating the map is O(n³). Summing C(k,2) over buckets is O(n²). Space is O(n²) for the map of substring signatures.
E · Edge cases and pitfalls6s = "a" — Only one substring; its bucket has count 1 → C(1,2)=0 → returns 0.Explore edge casesHide edge cases
Edge cases handled
s = "a"Only one substring; its bucket has count 1 → C(1,2)=0 → returns 0.s = "abcd"All signatures distinct, every bucket count is 1 → sum of C(1,2) = 0.s = "kkkk"Buckets of size 4,3,2,1 → 6+3+1+0 = 10. Shows C(k,2) growing quadratically with repeats.
Pitfalls
- Adding k instead of C(k,2) — undercounts every bucket of size ≥ 3.
- Keying on the raw substring instead of a canonical signature — anagrams never collide.
- Using float division (k*(k-1)/2 in Python) — use // to stay an integer.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic int sherlockAndAnagrams(String s) { Map<String, Integer> count = new HashMap<>(); int n = s.length(); for (int i = 0; i < n; i++) { for (int j = i + 1; j <= n; j++) {Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Returning k instead of C(k,2)
A bucket of k mutual anagrams yields k(k-1)/2 unordered pairs, not k. On 'kkkk' the length-1 bucket of 4 gives 6 pairs, but adding k would give 4.
Explore the code comparisonHide the code comparison
Returning k instead of C(k,2)
A bucket of k mutual anagrams yields k(k-1)/2 unordered pairs, not k. On 'kkkk' the length-1 bucket of 4 gives 6 pairs, but adding k would give 4.
Don't write this
return sum(count.values()) # adds bucket sizes, not pairsWrite this instead
return sum(k * (k - 1) // 2 for k in count.values())2Keying on the raw substring
If the map key is the substring itself, 'ab' and 'ba' get separate buckets and are never recognised as anagrams. The key must be a canonical form.
Explore the code comparisonHide the code comparison
Keying on the raw substring
If the map key is the substring itself, 'ab' and 'ba' get separate buckets and are never recognised as anagrams. The key must be a canonical form.
Don't write this
String sig = s.substring(i, j); // 'ab' and 'ba' differ
count.merge(sig, 1, Integer::sum);Write this instead
char[] c = s.substring(i, j).toCharArray();
Arrays.sort(c);
String sig = new String(c); // 'ab' and 'ba' both → 'ab'
count.merge(sig, 1, Integer::sum);3Float division in the pair count
In Python, k*(k-1)/2 produces a float (e.g. 6.0), which is wrong for a count and can break equality checks. Use integer division //.
Explore the code comparisonHide the code comparison
Float division in the pair count
In Python, k*(k-1)/2 produces a float (e.g. 6.0), which is wrong for a count and can break equality checks. Use integer division //.
Don't write this
pairs += k * (k - 1) / 2 # 6.0, a floatWrite this instead
pairs += k * (k - 1) // 2 # 6, an intRevise
Carry the pattern into the interview
Pattern to remember
Group by canonical signature, then count pairs with C(k,2)
Code pattern to memorize
count = {}
for each substring sub:
sig = canonical(sub) # sorted chars OR letter-count tuple
count[sig] += 1
return sum(k*(k-1)//2 for k in count.values())Rules to remember
- 1Anagram ⇔ identical letter-frequency signature. Pick a canonical form so all anagrams collapse to the same key.
- 2From a group of
kmutually-anagram substrings, the number of unordered pairs isC(k,2) = k(k-1)/2— notk. - 3Only compare substrings of the same length; using sorted-chars or a count tuple as the key enforces that automatically (different lengths → different keys).
- 4Count substrings by content and position: the two 'a's in
"aa"are distinct substrings, so they form one valid pair. - 5Use a frequency tuple (or sorted string) as the key — never the raw substring, or anagrams won't collide.
Use this pattern when
- Counting or grouping items that are equal up to reordering (anagrams)
- 'How many pairs share property X' where X has a canonical signature
- Bucketing strings/arrays by a normalized key, then combining within buckets
- Any problem reducible to 'group, then count pairs per group'
Choose another pattern when
- Need the actual pairs, not just the count → store members per bucket (more memory)
- Substrings must be contiguous AND you need longest/shortest → different (often two-pointer) technique
- Comparing whole strings only, not all substrings → a single grouping pass, no n² enumeration
- Order matters (not anagrams) → key on the raw string, not a signature
Interview takeaway
The reusable move is 'reduce equivalence to a canonical key, bucket by it, then count pairs with C(k,2)'. The same shape powers Group Anagrams, counting equal rows/columns, and any 'how many pairs are equivalent' question.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternCanonical 'group by signature, count with C(k,2)'. Reduce an equivalence (anagram) to a canonical key, bucket in a hash map, then convert bucket sizes to pair counts combinatorially. Same shape as Group Anagrams and counting equal rows/columns in a matrix.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Group AnagramsSame signature-bucketing, but return the groups instead of a pair count.
- Valid AnagramThe atomic check underneath: compare two letter-count signatures.
- Find All Anagrams in a StringComing soonFixed-length anagram matching via a sliding-window frequency count.
- Permutation in StringWindow frequency match — does any window equal the pattern's signature?