Tim Varley Logo
Tim Varley Principal AI Engineer and Tech Leader
Problem #24 hard

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?

View on Project Euler

Implementations

cpp
// 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_MODE
int 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_MODE
View on GitHub
O(n) time complexity

Solution 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