Problem 8: Largest Product in a Series
View on Project EulerProject Euler Problem 8 Solution
EulerSolve provides an optimized solution for Project Euler Problem 8, Largest Product in a Series, with C++, Python, Java, and a step-by-step mathematical explanation.
Problem Summary The input is the fixed 1000-digit decimal string from the problem statement. For a chosen window length \(w\), the task is to find the largest product of \(w\) consecutive digits. In the original Euler instance, \(w = 13\). Mathematically, we are evaluating a function \(M(w)\) on one fixed digit sequence. The implementations also define sensible boundary behavior: if \(w \le 0\) or \(w\) is longer than the string itself, the returned value is \(0\). Mathematical Approach Let the digits be \(d_0, d_1, \dots, d_{m-1}\), where each \(d_i \in \{0,1,\dots,9\}\) and \(m=1000\) for the Euler input. Window Products Form the Entire Search Space For every valid starting position \(i\), define the window product $P_i=\prod_{t=0}^{w-1} d_{i+t}, \qquad 0 \le i \le m-w.$ The desired value is therefore $M(w)=\max_{0 \le i \le m-w} P_i.$ This formula already explains why a complete scan is correct: every block of \(w\) consecutive digits appears exactly once as one of these windows, so taking the maximum over all valid starts cannot miss the optimum. Zeros Split the Search into Independent Runs Because all digits are nonnegative, a zero destroys the whole product of any window that touches it....
Detailed mathematical approach
Problem Summary
The input is the fixed 1000-digit decimal string from the problem statement. For a chosen window length \(w\), the task is to find the largest product of \(w\) consecutive digits. In the original Euler instance, \(w = 13\).
Mathematically, we are evaluating a function \(M(w)\) on one fixed digit sequence. The implementations also define sensible boundary behavior: if \(w \le 0\) or \(w\) is longer than the string itself, the returned value is \(0\).
Mathematical Approach
Let the digits be \(d_0, d_1, \dots, d_{m-1}\), where each \(d_i \in \{0,1,\dots,9\}\) and \(m=1000\) for the Euler input.
Window Products Form the Entire Search Space
For every valid starting position \(i\), define the window product
$P_i=\prod_{t=0}^{w-1} d_{i+t}, \qquad 0 \le i \le m-w.$
The desired value is therefore
$M(w)=\max_{0 \le i \le m-w} P_i.$
This formula already explains why a complete scan is correct: every block of \(w\) consecutive digits appears exactly once as one of these windows, so taking the maximum over all valid starts cannot miss the optimum.
Zeros Split the Search into Independent Runs
Because all digits are nonnegative, a zero destroys the whole product of any window that touches it. If the string is decomposed into maximal zero-free runs \(R_1,\dots,R_s\), and a typical run is written as \(R_j=(e_0,e_1,\dots,e_{r_j-1})\), then every window either lies completely inside one run or has product \(0\).
That lets us rewrite the problem as
$M(w)=\max\left(0,\max_j M_j(w)\right), \qquad M_j(w)=\max_{0 \le i \le r_j-w}\prod_{t=0}^{w-1} e_{i+t},$
where a run contributes only when \(r_j \ge w\). This is the key structural fact of Problem 8: zeros do not merely lower a candidate product, they partition the whole search into independent nonzero segments plus the fallback value \(0\).
Consecutive Windows on a Zero-Free Run
Inside a run with no zeros, consecutive window products satisfy an exact recurrence. If
$P_i=\prod_{t=0}^{w-1} e_{i+t},$
then for \(0 \le i < r_j-w\),
$P_{i+1}=P_i \cdot \frac{e_{i+w}}{e_i}.$
This identity is valid precisely because \(e_i \neq 0\). It is the natural mathematical basis for a rolling-product or sliding-window optimization. The provided implementations do not use this recurrence; they recompute each \(P_i\) from scratch. That choice keeps the code simple and avoids special zero bookkeeping, which is perfectly reasonable for a 1000-digit input.
Worked Examples from the Verified Checks
For the short sequence \(123456789\) with \(w=2\), the window products are
$2, 6, 12, 20, 30, 42, 56, 72,$
so the maximum is \(72\), attained by the final window \(89\).
For the 1000-digit Euler string with \(w=4\), one verified optimal block is \(9989\), giving
$9 \times 9 \times 8 \times 9 = 5832.$
For the original target \(w=13\), the maximizing block is \(5576689664895\), and its product is
$5 \times 5 \times 7 \times 6 \times 6 \times 8 \times 9 \times 6 \times 6 \times 4 \times 8 \times 9 \times 5 = 23514624000.$
Why Direct Multiplication Is Enough Here
The largest possible product of 13 decimal digits is \(9^{13}=2541865828329\), well below the range of signed 64-bit integers. So there is no overflow problem for the Euler parameter.
There are exactly \(1000-13+1=988\) candidate windows, and the straightforward method performs \(13\) digit multiplications per window. That is only
$988 \times 13 = 12844$
digit multiplications, plus trivial loop overhead. For such a small fixed workload, the simpler \(O(mw)\) scan is mathematically transparent and practically fast.
How the Code Works
Input Representation and Boundary Cases
The C++, Python, and Java implementations store the 1000-digit number as one immutable string literal. They all accept a window length parameter, and they all return \(0\) immediately when the requested window is nonpositive or longer than the digit string.
Exhaustive Window Scan
The implementation then loops over every legal starting position \(i\). For each start, it initializes a product accumulator to \(1\), multiplies the \(w\) digits in positions \(i,i+1,\dots,i+w-1\), and compares that product with the best value seen so far.
In other words, the outer loop enumerates the windows from the formula for \(M(w)\), and the inner loop reconstructs the invariant “the accumulator equals the product of the current window” from scratch each time.
Cross-Language Consistency
All three versions implement the same mathematics. The C++ and Java versions use 64-bit integer arithmetic; Python integers are arbitrary precision, so the same computation is still exact. The code also includes the same two checkpoints in every language: the toy case \((123456789, 2) \mapsto 72\), and the 1000-digit check with \(w=4\) giving \(5832\).
Complexity Analysis
For a digit string of length \(m\), there are \(m-w+1\) windows, and each one is recomputed using \(w\) multiplications. The running time is therefore
$O((m-w+1)w)=O(mw).$
The memory usage is \(O(1)\) beyond the stored digit string and a few scalar variables.
For the Euler input with \(m=1000\) and \(w=13\), this means 988 window evaluations and 12,844 digit multiplications. A rolling-product implementation on zero-free runs could reduce the time to \(O(m)\), but the current exhaustive scan is already more than sufficient.
Footnotes and References
- Problem page: Project Euler - Problem 8
- Product in mathematics: Wikipedia - Product (mathematics)
- Zero and its algebraic role: Wikipedia - Zero (mathematics)
- Sliding-window technique: GeeksforGeeks - Window Sliding Technique
- Asymptotic complexity notation: Wikipedia - Big O notation