Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Find the 1-based rank of a string among all its permutations in dictionary order. For each position, count how many characters to its right are smaller — each such smaller character could lead a block of (remaining-length)! permutations that all sort before the given string. Sum those blocks and add 1. With duplicate characters you must divide each block by the factorials of the repeated counts. Distinct case is O(n²) or O(n·26); answer rank = 1 + Σ (smaller-to-right) × (remaining)!.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you can convert 'position in sorted order' into a per-position combinatorial count, and whether you correctly divide by repeated-character factorials instead of assuming all characters are distinct.
Where candidates slip
Counting smaller characters over the whole string instead of only to the right, forgetting the +1 for 1-based rank, or ignoring duplicates and over-counting.
How to stand out
Derive the formula from the factorial number system, handle duplicates with the Π freq! division, and flag the overflow with a modular-inverse or big-integer plan.
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 previewAt position i, char s[i]Duplicates?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 — Generate and Sort Permutations
1Hint 1Explore hintHide hint
Generate every distinct permutation, sort them lexicographically, and the index of s (plus 1) is the rank.
2Hint 2Explore hintHide hint
Deduplicate with a set so repeated characters don't inflate the count.
3Hint 3Explore hintHide hint
There are up to n! permutations — this is only viable for very small n, but it nails down exactly what 'rank' means.
A · Core idea
The definition-driven baseline literally builds the dictionary.
The literal method. Produce all permutations of the characters, drop duplicates into a sorted structure, and find where s lands. Its 1-based position is the rank.
Why dedup matters. With repeated characters, naive permutation generation yields the same string multiple times. A set (or sorted unique list) collapses those so each distinct arrangement counts once — matching the problem's definition.
Why it can't scale. The number of permutations is n! (or n!/Πfreq! distinct ones). For n = 12 that's already ~479 million. Generating and sorting them is hopeless past tiny n. The value of this approach is purely as a correctness oracle and as a way to see that rank = (position in the sorted list) + 1 — the very quantity the combinatorial formula computes directly.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
s = "acb"Array state
sorted
abc, acb, bac, bca, cab, cba
2C · Code with teaching comments
Reference implementation previewimport java.util.TreeSet; public class BruteForceRank { private final TreeSet<String> perms = new TreeSet<>(); Explore the full codeHide the full code
D · Why it works and complexityO(n! · n log(n!)) timeO(n! · n) spaceOpen the invariant and proofExplore the proofHide the proof
Brute force shows that rank equals the count of distinct permutations strictly smaller than s, plus one. The optimal approach computes that count without materializing the list — by counting, position by position, how many smaller permutations exist.
ComplexityO(n! · n log(n!)) time, O(n! · n) space — Generating permutations is O(n!) of them, each of length n; sorting that many length-n strings is O(n! · n log(n!)). Storing them is O(n! · n). Factorial growth caps this at roughly n ≤ 10.
E · Edge cases and pitfalls3Not deduplicating — repeated characters inflate the rank.Explore edge casesHide edge cases
Pitfalls
- Not deduplicating — repeated characters inflate the rank.
- Returning a 0-based index — off by one from the expected 1-based rank.
- Trying it for n beyond ~10 — n! permutations exhaust time and memory.
Approach 2 of 2 · Optimal
Factorial Counting with Duplicate Handling (Optimal)
1Hint 1Explore hintHide hint
Walk left to right. At position i, every permutation that puts a character smaller than s[i] here (and is otherwise any arrangement of the rest) sorts before s. How many such arrangements are there?
2Hint 2Explore hintHide hint
If the remaining characters are all distinct, fixing one smaller character leaves (n-1-i)! arrangements. Sum (count of smaller chars to the right) × (n-1-i)! over all i, then add 1.
3Hint 3Explore hintHide hint
With duplicates, (n-1-i)! over-counts because swapping identical characters yields the same string. Divide by the product of factorials of the remaining character frequencies.
A · Core idea
The optimal solution counts the permutations smaller than s directly, treating the answer as a number written in the factorial base.
The per-position question. Scan s left to right. Standing at position i, ask: how many distinct permutations agree with s on positions 0..i−1 but place a smaller character at position i? Every such permutation is lexicographically less than s, and every permutation less than s is captured at exactly one such position (the first place it diverges). So summing these counts over all i gives the number of permutations before s; add 1 for the 1-based rank.
Counting one position (distinct case). Let smaller = the number of characters strictly less than s[i] among the remaining characters (positions i..n−1). Choosing any one of those smaller characters to sit at position i leaves n−1−i characters to arrange freely, which is (n−1−i)! orderings. So position i contributes smaller × (n−1−i)!.
The duplicate correction. When the remaining characters contain repeats, (n−1−i)! counts arrangements that are actually identical (swapping two equal characters changes nothing). The number of distinct arrangements of a multiset with frequencies freq[c] is (n−1−i)! / Π_c freq[c]!. So each block becomes smaller × (n−1−i)! / Π_c freq[c]!, where freq is the frequency table of the suffix s[i..n−1]. After processing position i, decrement freq[s[i]] so the table always reflects 'characters still available'.
Why this is the factorial number system. The sequence of smaller counts is exactly the factorial-base digits of rank − 1: digit i lies in [0, n−1−i] and carries weight (n−1−i)!. Ranking a permutation is reading off those digits; un-ranking (finding the k-th permutation) is the inverse.
Overflow reality. Factorials grow past 64-bit at 21!. For larger inputs, either use big integers, or — if only rank mod M is needed — precompute factorials and modular inverses modulo a prime so the division by Π freq! becomes multiplication by modular inverses.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
s = "aba"Array state
i=0 'a'
smaller to right = 0 → 0 × 2! = 0
2C · Code with teaching comments
D · Why it works and complexityO(n² ) or O(n·Σ) timeO(Σ) spaceOpen the invariant and proofExplore the proofHide the proof
Rank is a counting problem, not a sorting problem. Each position contributes an independent block of 'permutations that diverge here and go smaller', and the factorial weight is just the number of ways to fill the tail. Duplicates only change the weight by deflating it with Π freq! — the structure is identical.
ComplexityO(n² ) or O(n·Σ) time, O(Σ) space — For each of the n positions you count smaller characters in the suffix (O(Σ) with a frequency table, or O(n) by scanning), and compute a factorial-ratio block. That's O(n·Σ) or O(n²). The frequency table is O(Σ) space; factorials are precomputed in O(n).
E · Edge cases and pitfalls7s = "a" — Single character → only permutation → rank 1.Explore edge casesHide edge cases
Edge cases handled
s = "a"Single character → only permutation → rank 1.s = "cba"Largest permutation of {a,b,c} → rank 6 (= 3!).s = "aaa"Only one distinct permutation → rank 1; duplicate division cancels everything.
Pitfalls
- Counting smaller characters over the whole string instead of only the suffix.
- Ignoring duplicates — without the Π freq! division you over-count and inflate the rank.
- Using 64-bit factorials beyond 20! — silent overflow to wrong (often negative) values.
- Forgetting the +1, returning a 0-based count instead of a rank.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewimport java.math.BigInteger; public class LexRank { public BigInteger rank(String s) { int n = s.length();Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Smaller count over the whole string
Only characters still available (to the right) can validly sit at position i. Counting smaller characters that already appeared in the prefix double-counts and corrupts the rank.
Explore the code comparisonHide the code comparison
Smaller count over the whole string
Only characters still available (to the right) can validly sit at position i. Counting smaller characters that already appeared in the prefix double-counts and corrupts the rank.
Don't write this
smaller = sum(1 for ch in s if ch < s[i]) # whole string — wrongWrite this instead
smaller = sum(freq[ch] for ch in range(ord(s[i]))) # only remaining suffix2Ignoring duplicate characters
(n-1-i)! counts arrangements that are identical when characters repeat. Without dividing by Π freq! the block over-counts and the rank comes out too large.
Explore the code comparisonHide the code comparison
Ignoring duplicate characters
(n-1-i)! counts arrangements that are identical when characters repeat. Without dividing by Π freq! the block over-counts and the rank comes out too large.
Don't write this
block = smaller * factorial(n - 1 - i) # over-counts duplicatesWrite this instead
denom = 1
for f in freq:
denom *= factorial(f)
block = smaller * factorial(n - 1 - i) // denom364-bit factorial overflow
21! exceeds the 64-bit range, so for longer strings the factorial silently wraps and every block is wrong. Use big integers or modular arithmetic.
Explore the code comparisonHide the code comparison
64-bit factorial overflow
21! exceeds the 64-bit range, so for longer strings the factorial silently wraps and every block is wrong. Use big integers or modular arithmetic.
Don't write this
long f = 1; for (int k = 2; k <= n; k++) f *= k; // overflows at 21!Write this instead
BigInteger f = BigInteger.ONE;
for (int k = 2; k <= n; k++) f = f.multiply(BigInteger.valueOf(k));Revise
Carry the pattern into the interview
Pattern to remember
Factorial number system: count permutations that sort before each prefix
Code pattern to memorize
DISTINCT: rank = 1 + Σ_i smallerRight(i) * (n-1-i)!
WITH DUPLICATES: rank = 1 + Σ_i smallerRight(i) * (n-1-i)! / Π_c (freq[c]!)
where freq is the frequency of the suffix s[i..n-1], updated as you advanceRules to remember
- 1Rank is 1-based — the smallest permutation has rank 1, so the final answer adds 1.
- 2
smallerRight(i)counts characters strictly less thans[i]among positionsi+1..n-1, not the whole string. - 3Each smaller character fixes position
iand frees(n-1-i)!arrangements of the rest — that's why the factorial appears. - 4Duplicates: divide each block by
Π freq[c]!over the current suffix multiset, or you over-count identical arrangements. - 5Factorials explode —
21!overflows 64-bit. Use big integers, or compute the rank modulo a prime with modular inverses.
Use this pattern when
- Rank / un-rank a permutation in lexicographic order
- Map a permutation to/from an index (factorial number system)
- Find the k-th permutation, or how many permutations precede a given one
- Any 'position in dictionary of all rearrangements' question
Choose another pattern when
- You only need the *next* permutation → next_permutation in-place swap, not full ranking
- Alphabet/length tiny and you just need any one answer → brute force all permutations
- Strings are not permutations of each other → ranking among permutations is undefined
- You need rank among arbitrary strings of a length, not permutations → positional base-Σ counting instead
Interview takeaway
Lexicographic rank is the factorial number system in disguise: digit i is 'how many unused smaller characters remain', weighted by the factorial of the remaining length. Duplicates just divide out the indistinguishable rearrangements.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternFactorial Number System / combinatorial ranking. Recognise it whenever a problem asks for the position of a permutation in sorted order, the k-th permutation, or how many arrangements precede one — all are factorial-base digit reads, with duplicates handled by dividing out Π freq!.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- k-th Permutation Sequence (un-rank)Coming soonInvert the formula: peel off factorial-base digits of k−1 to pick each character in turn.
- Next PermutationComing soonIf you only need the immediately following permutation, do the in-place pivot-swap-reverse in O(n).
- Rank of a String mod p (large n)Coming soonPrecompute factorials and modular inverses; replace division by Π freq! with multiplication by inverses.
- Rank Among All Strings of Length nComing soonNot a permutation problem — use positional base-Σ counting instead of factorials.