The problem at hand is:
Given a string. Tell its rank among all its permutations sorted lexicographically.
The question can be at
From GeeksforGeeks:
Given a string, find its rank among all its permutations sorted lexicographically. For example, rank of “abc” is 1, rank of “acb” is 2, and rank of “cba” is 6.
For simplicity, let us assume that the string does not contain any duplicated characters.
One simple solution is to initialize rank as 1, generate all permutations in lexicographic order. After generating a permutation, check if the generated permutation is same as given string, if same, then return rank, if not, then increment the rank by 1. The time complexity of this solution will be exponential in worst case. Following is an efficient solution.
Let the given string be “STRING”. In the input string, ‘S’ is the first character. There are total 6 characters and 4 of them are smaller than ‘S’. So there can be 4 * 5! smaller strings where first character is smaller than ‘S’, like following
R X X X X X I X X X X X N X X X X X G X X X X X
Now let us Fix S’ and find the smaller strings staring with ‘S’.
Repeat the same process for T, rank is 4*5! + 4*4! +…
Now fix T and repeat the same process for R, rank is 4*5! + 4*4! + 3*3! +…
Now fix R and repeat the same process for I, rank is 4*5! + 4*4! + 3*3! + 1*2! +…
Now fix I and repeat the same process for N, rank is 4*5! + 4*4! + 3*3! + 1*2! + 1*1! +…
Now fix N and repeat the same process for G, rank is 4*5! + 4*4 + 3*3! + 1*2! + 1*1! + 0*0!
Rank = 4*5! + 4*4! + 3*3! + 1*2! + 1*1! + 0*0! = 597
Since the value of rank starts from 1, the final rank = 1 + 597 = 598
There is an O(n|Σ|) algorithm to find the rank of a string of length n in the list of its permutations. Here, Σ is the alphabet.
Every permutation which is ranked below s can be written uniquely in the form pcx; where:
We can count the permutations included in each of these classes by iterating through each prefix of s in increasing order of length, while maintaining the frequency of the characters appearing in the remaining part of s, as well as the number of permutations x represents. The details are left to the reader.
This is assuming the arithmetic operations involved take constant time; which it wont; since the numbers involved can have nlog|Σ| digits. With this consideration, the algorithm will run in O(n2 log|Σ| log(nlog|Σ|)). Since we can add, subtract, multiply and divide two d-digit numbers in O(dlogd).
typedef long long int lli;
lli rank(string s){
int n = s.length();
vector<lli> factorial(n+1,1);
for(int i = 1; i <= n; i++)
factorial[i] = i * factorial[i-1];
vector<int> freq(26);
lli den = 1;
lli ret = 0;
for(int i = n-1; i >= 0; i--){
int si = s[i]-'a';
freq[si]++;
den *= freq[si];
for(int c = 0; c < si; c++)
if(freq[c] > 0)
ret += factorial[n-i-1] / (den / freq[c]);
}
return ret + 1;
}
This is similar to the quickselect algorithm. In an unsorted array of integers, find the index of some particular array element. The partition element would be the given string.
Edit:
Actually it is similar to partition method done in QuickSort. The given string is the partition element.Once all permutations are generated, the complexity to find the rank for strings with length k would be O(nk). You can generate string permutations using recursion and store them in a linked list. You can pass this linked list to the partition method.
Here's the java code to generate all String permutations:
private static int generateStringPermutations(String name,int currIndex) {
int sum = 0;
for(int j=name.length()-1;j>=0;j--) {
for(int i=j-1;((i<j) && (i>currIndex));i--) {
String swappedString = swapCharsInString(name,i,j);
list.add(swappedString);
//System.out.println(swappedString);
sum++;
sum = sum + generateStringPermutations(swappedString,i);
}
}
return sum;
}
Edit:
Generating all permutations is costly. If a string contains distinct characters, the rank can be determined without generating all permutations. Here's the link.
This can be extended for cases where there are repeating characters.
Instead of x * (n-1)! which is for distinct cases mentioned as in the link,
For repeating characters it will be:
if there is 1 character which is repeating twice,
x* (n-1)!/2!
Let's take an example. For string abca the combinations are:
aabc,aacb,abac,abca,acab,acba,baac,baca,bcaa,caab,caba,cbaa (in sorted order)
Total combinations = 4!/2! = 12
if we want to find rank of 'bcaa' then we know all strings starting with 'a' are before which is 3! = 6.
Note that because 'a' is the starting character, the remaining characters are a,b,c and there are no repetitions so it is 3!. We also know strings starting with 'ba' will be before which is 2! = 2 so it's rank is 9.
Another example. If we want to find the rank of 'caba':
All strings starting with a are before = 6. All strings starting with b are before = 3!/2! = 3 (Because once we choose b, we are left with a,a,c and because there are repetitions it is 3!/2!. All strings starting with caa will be before which is 1
So the final rank is 11.