Problem 178: Step Numbers
View on Project EulerProject Euler Problem 178 Solution
EulerSolve provides an optimized solution for Project Euler Problem 178, Step Numbers, with C++, Python, Java, and a step-by-step mathematical explanation.
Problem Summary Project Euler 178 asks for the number of step numbers below \(10^{40}\) that are pandigital, meaning that every digit \(0,1,\dots,9\) appears at least once. A step number is a positive decimal integer whose adjacent digits differ by exactly 1, so every valid number traces a walk such as \(45654\) or \(1234567890\) along the digit line. Because the bound is \(10^{40}\), we must count all valid lengths from 1 through 40, not only 40-digit numbers. Leading zero is forbidden, and any number with fewer than 10 digits is automatically too short to be pandigital, but the dynamic program can treat all lengths uniformly. Mathematical Approach The natural model is to view the digits as the vertices of the path graph $0-1-2-3-4-5-6-7-8-9.$ A step number of length \(\ell\) is exactly a walk of \(\ell-1\) edges on this graph, starting from one of the vertices \(1,\dots,9\). The step condition itself is easy; the real issue is remembering which digits have already appeared so that we know when the walk has become pandigital. Digit Graph and State Space Let \(M\subseteq\{0,1,\dots,9\}\) be the set of digits seen so far, and let \(d\) be the last digit....
Detailed mathematical approach
Problem Summary
Project Euler 178 asks for the number of step numbers below \(10^{40}\) that are pandigital, meaning that every digit \(0,1,\dots,9\) appears at least once. A step number is a positive decimal integer whose adjacent digits differ by exactly 1, so every valid number traces a walk such as \(45654\) or \(1234567890\) along the digit line.
Because the bound is \(10^{40}\), we must count all valid lengths from 1 through 40, not only 40-digit numbers. Leading zero is forbidden, and any number with fewer than 10 digits is automatically too short to be pandigital, but the dynamic program can treat all lengths uniformly.
Mathematical Approach
The natural model is to view the digits as the vertices of the path graph
$0-1-2-3-4-5-6-7-8-9.$
A step number of length \(\ell\) is exactly a walk of \(\ell-1\) edges on this graph, starting from one of the vertices \(1,\dots,9\). The step condition itself is easy; the real issue is remembering which digits have already appeared so that we know when the walk has become pandigital.
Digit Graph and State Space
Let \(M\subseteq\{0,1,\dots,9\}\) be the set of digits seen so far, and let \(d\) be the last digit. Define
$F(\ell,M,d)=\#\{(a_1,\dots,a_\ell): a_\ell=d,\ |a_i-a_{i+1}|=1,\ \{a_1,\dots,a_\ell\}=M\}.$
The invariant is exact: every legal prefix belongs to one and only one state, determined by its length, its final digit, and the set of distinct digits already visited. In the implementations, the set \(M\) is encoded by a 10-bit mask, so adding a new digit is just setting one more bit.
Initialization and Boundary Digits
At length 1, the only allowed starting digits are \(1,2,\dots,9\). Therefore
$F(1,\{d\},d)=1\qquad(1\le d\le 9),$
and all other states with \(\ell=1\) are zero. In particular, there is no initial state ending in \(0\), because that would represent a leading zero.
The path-graph viewpoint also explains the edge cases immediately. Digit \(0\) can only move to \(1\), digit \(9\) can only move to \(8\), and each interior digit \(1,\dots,8\) has exactly two possible continuations.
Recurrence from the Step Rule
Suppose a prefix is in state \((\ell,M,d)\). The next digit must be a neighbor of \(d\), so each valid extension goes to one of the states
$F(\ell+1,M\cup\{d-1\},d-1)\mathrel{+}=F(\ell,M,d)\qquad(d>0),$
$F(\ell+1,M\cup\{d+1\},d+1)\mathrel{+}=F(\ell,M,d)\qquad(d<9).$
This recurrence is exact rather than heuristic. Every step number of length \(\ell+1\) has a unique prefix of length \(\ell\), so the transition neither misses any object nor counts any object twice.
Recovering the Final Count
Let
$M_{\mathrm{all}}=\{0,1,\dots,9\}.$
A number contributes to the answer precisely when its visited set is \(M_{\mathrm{all}}\). Since the problem asks for all step numbers below \(10^{40}\), the required total is
$\text{Answer}=\sum_{\ell=1}^{40}\sum_{d=0}^{9}F(\ell,M_{\mathrm{all}},d).$
Lengths below 10 contribute zero automatically, because no 9-digit number can contain all ten decimal digits. Keeping those lengths in the summation makes the formulation cleaner and matches the implementations exactly.
Worked Example
Consider the prefix \(45654\). It is a step number because all adjacent differences are 1. Its state is
$\ell=5,\qquad d=4,\qquad M=\{4,5,6\}.$
From there the next digit can only be \(3\) or \(5\). Appending \(3\) produces \(456543\), which moves to \((6,\{3,4,5,6\},3)\); appending \(5\) produces \(456545\), which moves to \((6,\{4,5,6\},5)\). This is exactly why both pieces of information are necessary: the last digit determines the legal next move, while the visited set determines whether the number is already pandigital.
At the other extreme, numbers such as \(1234567890\) and \(9876543210\) already have \(M_{\mathrm{all}}\) at length 10, so they are immediate contributors to the final total.
How the Code Works
The C++, Python, and Java implementations all realize this same three-dimensional dynamic program. They allocate a table indexed by length, visited-digit mask, and final digit, and they seed the nine legal one-digit starting states.
Forward DP Sweep
The table is filled in increasing order of length. Whenever a state count is zero, the implementation skips it. Otherwise it forwards that count to the next layer, once to \(d-1\) if \(d>0\) and once to \(d+1\) if \(d<9\), while updating the visited-digit mask.
Because the digit graph is just a line, each state has at most two outgoing transitions, and the endpoints have only one. That is why the full computation up to 40 digits is completely practical once the digit set is compressed into a bitmask.
Aggregation of the Pandigital States
After all layers have been filled, the implementation scans every length from 1 through 40 and adds the counts whose mask has all ten bits set. Summing over all end digits is necessary because the problem does not prescribe the final digit.
The implementations use ordinary integer arithmetic rather than modular arithmetic: the goal is the exact count, and the 40-digit limit keeps the total within 64-bit range.
Validation Strategy
The C++ implementation includes an additional checkpoint for a small digit bound: it brute-forces all step-number walks by depth-first search and confirms that the total agrees with the dynamic program. The Python and Java implementations keep the same DP core but omit that explicit cross-check layer.
Complexity Analysis
There are \(40\) possible lengths, \(2^{10}=1024\) possible visited-digit sets, and \(10\) possible final digits, so the total number of DP states is
$40\cdot 2^{10}\cdot 10=409{,}600.$
Each state generates at most two transitions. Therefore the running time is \(O(N\cdot 2^{10}\cdot 10)\) for a general upper bound \(N\), and with \(N=40\) the computation is very small.
The current implementations store every length layer, so the memory usage is also \(O(N\cdot 2^{10}\cdot 10)\). A rolling-array variant could reduce that to \(O(2^{10}\cdot 10)\), but the larger table is still tiny and keeps the code straightforward.
Footnotes and References
- Project Euler problem page: https://projecteuler.net/problem=178
- Dynamic programming: Wikipedia - Dynamic programming
- Bitmasking: Wikipedia - Mask (computing)
- Path graph: Wikipedia - Path graph
- Walks in graph theory: Wikipedia - Walk (graph theory)