Project Euler Problem 24 Statement
A permutation is an ordered arrangement of objects. For example, 3124 is one possible permutation of the digits 1, 2, 3 and 4. If all the permutations are listed numerically or alphabetically, we call it lexicographic order. The lexicographic permutations of 0, 1 and 2 are:
012 021 102 120 201 210
What is the millionth lexicographic permutation of the digits 0, 1, 2, 3, 4, 5, 6, 7, 8 and 9?
Solution
Solving this problem and studying the second solution below can help you understand the recursive process. The first solution is effective but lacks the scope to solve the HackerRank version. Also, by convention, these permutations count from zero, so we subtract 1 from the target (1,000,000).
Slow and easy itertools
version
from itertools import permutations
# Find the one millionth permutation of '0123456789'
print (''.join(list(permutations('0123456789',10))[999999]))
A more efficient and reasonable approach
Let’s investigate an example, step–by–step, to get an idea of how we can determine our permutation one element at a time.
An example for [A B C D]
The complete lexicographic order for 4 symbols: A, B, C, D Total permutations = 4! or 3!x4 = 24
Col 00 Col 01 Col 02 Col 03 00 [A B C D] 06 [B A C D] 12 [C A B D] 18 [D A B C] 01 [A B D C] 07 [B A D C] 13 [C A D B] 19 [D A C B] 02 [A C B D] 08 [B C A D] 14 [C B A D] 20 [D B A C] 03 [A C D B] 09 [B C D A] 15 [C B D A] 21 [D B C A] 04 [A D B C] 10 [B D A C] 16 [C D A B] 22 [D C A B] 05 [A D C B] 11 [B D C A] 17 [C D B A] 23 [D C B A]
Our goal is to find the 10th lexicographic permutation (index 9 starting from zero or the 10th entry). The data is arranged in a rectangular grid of 3! rows by 4 columns, i.e. 6×4.
If we just want the first element for the 10th arrangement we can see it’s in column 01, which makes sense because the first 3! arrangements will start with 'A', the second 3! with 'B', and so on. So, to calculate the column we simply integer divide our desired index, 9, by 3! which equals 1.
The column and the first element of our 10th lexicographic permutation:
9/3! = 1 with remainder 3 . That is, column 01 which all starts with 'B'. 'B' is the first element.
Now, create a new table without 'B' since it has now been placed in the first position:
The complete lexicographic order for 3 symbols: A, C, D Total permutations = 3! or 2!x3 = 6
Col 00 Col 01 Col 02 00 [A C D] 02 [C A D] 04 [D A C] 01 [A D C] 03 [C D A] 05 [D C A]
Remember our remainder of 3 from the previous calculation? Well, that’s now our new index for the next element.
3/2! = 1 with remainder 1. That is, column 01 which all starts with 'C'. 'C' is the second element.
And now to finish:
The complete lexicographic order for 2 symbols: A, D Total permutations = 2! or 1!x2 = 2
Col 00 Col 01 00 [A D] 01 [D A]
Again, recall our remainder of 1 from the previous calculation.
1/1 = 1 with remainder 0. That’s column 01 or 'D' and the last dependent position will be filled by 'A'. Our 10th lexicographic permutation is [B C D A].
Testing this algorithm on Project Euler 24
Now, using this algorithm, let’s get the first 4 digits to the millionth lexicographic permutation of the digits 0 through 9.
[0 1 2 3 4 5 6 7 8 9]
First digit: 999999/9! = 2 with remainder 274239. First digit is at index 2 (starting from 0) = '2'
2 [0 1 3 4 5 6 7 8 9]
Second digit: 274239/8! = 6 with remainder 32319. Second digit is at index 6 = '7'
2 7 [0 1 3 4 5 6 8 9]
Third digit: 32319/7! = 6 with remainder 2079. Third digit is at index 6 = '8'
2 7 8 [0 1 3 4 5 6 9]
Fourth digit: 2079/6! = 2 with remainder 639. Fourth digit is at index 2 = '3'
2 7 8 3 [0 1 4 5 6 9]
This method lends itself to recurrence nicely by building the result one element at a time. It will recursively call itself each time, reducing the size of the set by 1. The placed element from the previous iteration will be removed.
HackerRank version
HackerRank Project Euler 24 steps up the challenge by increasing the set from 10 to 13 characters (a–m) and proving 1,000 trials by finding the Nth lexicographic permutation, 1 ≤ N ≤ 13! (or 6,227,020,800).
Python Source Code
import math
def perm(n, s):
if len(s)==1: return s
q, r = divmod(n, math.factorial(len(s)-1))
return s[q] + perm(r, s[:q] + s[q+1:])
print (perm(int(input("Lexicographic permutation? "))-1, '0123456789'))
Last Word
- You can learn more about this method by visiting Factorial number system on Wikipedia.
- You can change the character set from "0123456789" to "abcdefghijklm" to test the HackerRank parameters.