Lexicographic Permutations
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 of 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?
Implementations
// https://projecteuler.net/problem=24// Lexicographic permutations
// 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 of 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?
// Answer: 2783915460
#include <algorithm>#include <chrono>#include <iostream>#include <string>
#include "simple_timer.h"
std::string lexicographic_permutations_cheat(std::string input){ int perm_count = 0; std::string result; std::sort(input.begin(), input.end()); do { result = input; } while(std::next_permutation(input.begin(), input.end()) && ++perm_count < 1000000);
return result;}
// This function finds the index of the smallest character// which is greater than 'first' and is present in str[l..h]int findCeil (char str[], char first, int l, int h){ // initialize index of ceiling element int ceilIndex = l;
// Now iterate through rest of the elements and find // the smallest character greater than 'first' for (int i = l+1; i <= h; i++) if (str[i] > first && str[i] < str[ceilIndex]) ceilIndex = i;
return ceilIndex;}
// @see - https://www.geeksforgeeks.org/lexicographic-permutations-of-string/// Following are the steps to print the permutations lexicographic-ally// 1. Sort the given string in non-decreasing order and print it.// The first permutation is always the string sorted in non-decreasing order.// 2. Start generating next higher permutation.// Do it until next higher permutation is not possible.// If we reach a permutation where all characters are sorted in non-increasing order,// then that permutation is the last permutation.//// Steps to generate the next higher permutation:// 1. Take the previously printed permutation and find the rightmost character in it,// which is smaller than its next character. Let us call this character as ‘first character’.// 2. Now find the ceiling of the ‘first character’.// Ceiling is the smallest character on right of ‘first character’,// which is greater than ‘first character’. Let us call the ceil character as ‘second character’.// 3. Swap the two characters found in above 2 steps.// 4. Sort the substring (in non-decreasing order) after the original index of ‘first character’.std::string lexicographic_permutations(const std::string& input){ std::string result = input; std::sort(result.begin(), result.end()); std::cout << "Input: " << input << std::endl; std::cout << "Result: " << result << std::endl; int perm_count = 0; while( ++perm_count < 1000000 ) { std::cout << perm_count << ") Result: " << result << std::endl; std::string::iterator itr; for( itr = (result.end() - 2); itr != result.begin(); --itr ) { std::cout << "Itr: [" << *itr << "][" << *(itr+1) << "]" << std::endl; if(*itr < *(itr+1)) { break; } } if( itr == result.begin() ) { break; } else { std::string::iterator swapsy = itr + 1; for( auto itr2 = itr + 1; itr2 != result.end(); itr2++ ) { if( *itr2 > *itr && *itr2 < *swapsy ) { swapsy = itr2; } }
std::cout << "Swap: " << *itr << " with " << *swapsy << std::endl; std::iter_swap(swapsy, itr); std::sort(itr+1, result.end()); std::cout << perm_count << ") Post Result: " << result << std::endl; } } return result;}
#if ! defined UNITTEST_MODEint main(int argc, char const *argv[]){ std::string solution = "2783915460"; // std::string digits("0123456789"); std::string digits("0132456789"); std::cout << "Solution: " << solution << std::endl;
// ------8<---- Cheat Mode---8<------- { simple_timer x("Lexicographics permutations (cheat mode)"); std::string cheat_answer = lexicographic_permutations_cheat(digits); std::cout << "Cheat mode" << std::endl; std::cout << "Answer: " << cheat_answer << std::endl; std::cout << "Correct?: " << (cheat_answer == solution ? "PASS" : "FAIL") << std::endl; } // ------8<---- Cheat Mode---8<-------
// ------8<----Non Cheat Mode---8<------- // { // std::string test_digits("012"); // simple_timer x("Lexicographics permutations ( non cheat mode)"); // std::string answer = lexicographic_permutations(digits); // std::cout << "Cheat mode" << std::endl; // std::cout << "Answer: " << answer << std::endl; // std::cout << "Correct?: " << (answer == solution ? "PASS" : "FAIL") << std::endl; // } // ------8<----Non Cheat Mode---8<-------
// std::cout << "Answer: " << lexicographic_permutations(digits) << std::endl;}#endif //#if ! defined UNITTEST_MODESolution Notes
Mathematical Background
This problem involves finding a specific permutation in lexicographic (dictionary) order. There are 10! = 3,628,800 total permutations of digits 0-9, and we want the millionth one (1,000,000th) when ordered lexicographically.
The key insight is using factorial number system to determine each digit position without generating all permutations. For n distinct items, there are (n-1)! permutations starting with each possible first digit.
Algorithm Analysis
Factorial-based approach:
- Start with digits [0,1,2,3,4,5,6,7,8,9] and target index 999,999 (0-based)
- For each position i from 0 to 9:
- Calculate factorial of (9-i) to determine how many permutations start with each possible digit
- Select the digit that corresponds to the target index range
- Remove the selected digit and continue with remaining digits
Example: For position 0, 9! = 362,880 permutations start with each digit. Index 999,999 ÷ 362,880 = 2 (integer division), so we select the 3rd digit (0-based indexing: 0,1,2 → digit 2).
Time complexity: O(n) where n=10, essentially constant time. Space complexity: O(n) for storing the digit list.
Key Insights
- The millionth permutation is 2,783,915,460
- Factorials provide an efficient way to navigate permutation space
- No need to generate all permutations - mathematical indexing suffices
- This demonstrates the power of combinatorial mathematics in computation
- The algorithm works for any lexicographic permutation index
Educational Value
This problem teaches:
- Permutation mathematics and factorial relationships
- Lexicographic ordering principles
- Efficient algorithms for large combinatorial spaces
- The difference between generating vs. indexing approaches
- Mathematical optimization in programming
- Working with factorials and combinatorial calculations
- When to use mathematical insight over brute force enumeration