LeetCode 372 - Super Pow
The problem asks us to compute: The complication is that the exponent b is extremely large. Instead of being given as a normal integer, it is provided as an array of digits.
Difficulty: 🟡 Medium
Topics: Math, Divide and Conquer
Solution
Problem Understanding
The problem asks us to compute:
$a^b \bmod 1337$
The complication is that the exponent b is extremely large. Instead of being given as a normal integer, it is provided as an array of digits. For example, if:
b = [1, 0, 2, 4]
then the exponent actually represents the number:
1024
The goal is to compute the modular exponentiation result efficiently without ever constructing the gigantic integer explicitly.
The modulus is fixed at 1337, which is relatively small. This is important because modular arithmetic allows us to keep intermediate numbers bounded and avoid overflow or huge computations.
A naive implementation might try to reconstruct the exponent and then compute:
a^b
directly. However, the exponent may contain up to 2000 digits, which means it can be astronomically large. Even storing that integer in standard numeric types is impossible.
The constraints reveal several important observations:
acan be as large as2^31 - 1, so repeated multiplication without modular reduction would overflow in many languages.b.length <= 2000, meaning the exponent can contain thousands of decimal digits.- Since the modulus is only
1337, modular exponentiation techniques become very effective. - The exponent digits are guaranteed to have no leading zeros, so the array always represents a valid positive integer.
Several edge cases are important:
a = 1, because any power of 1 remains 1.- Very large exponents, where constructing the number directly is impossible.
- Digits containing zeros, such as
[1,0], which represents 10 rather than separate operations. - Large values of
a, which require modular reduction early and often.
Approaches
Brute Force Approach
The brute force idea is straightforward. First, reconstruct the integer represented by the digit array b. Then compute:
(a^b) % 1337
using repeated multiplication or a standard power function.
This approach is correct because it directly evaluates the mathematical definition of the problem.
However, it immediately becomes impractical because the exponent can have up to 2000 digits. A 2000 digit number is vastly larger than what built in integer types can store efficiently. Even languages with arbitrary precision integers would struggle to compute such an enormous exponent directly.
Additionally, repeated multiplication would require an impossible number of operations.
Optimal Approach
The key insight is that modular arithmetic allows exponent decomposition.
Suppose the exponent digits are processed left to right. If we already computed:
a^x mod 1337
and we append another digit d, the new exponent becomes:
10x + d
Using exponent rules:
$a^{10x+d} = (a^x)^{10} \cdot a^d$
Taking modulo 1337 throughout gives:
$a^{10x+d} \bmod 1337 = \left((a^x)^{10} \bmod 1337\right) \cdot \left(a^d \bmod 1337\right) \bmod 1337$
This means we never need the full exponent. We only maintain the current modular result while processing digits one by one.
Efficient modular exponentiation, often called binary exponentiation or fast power, lets us compute powers in logarithmic time.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | Exponential / infeasible | Extremely large | Attempts to build and use the full exponent |
| Optimal | O(n log 10) | O(1) | Processes exponent digits incrementally using modular arithmetic |
Here, n is the number of digits in b.
Algorithm Walkthrough
- Initialize the modulus as
1337.
Since every computation is performed modulo 1337, we store this constant once and reuse it throughout the algorithm.
2. Reduce a modulo 1337.
This prevents unnecessary large intermediate values because:
$(a \bmod m)^k \bmod m = a^k \bmod m$
3. Initialize the running answer as 1.
This represents the value of:
a^0 mod 1337
before any exponent digits are processed.
4. Process each digit in b from left to right.
Suppose the current answer represents:
a^x mod 1337
and the next digit is d.
The new exponent becomes:
10x + d
- Raise the current answer to the 10th power modulo
1337.
This corresponds to:
(a^x)^10
We compute this efficiently using fast modular exponentiation.
6. Compute a^d mod 1337.
Since d is only a single decimal digit between 0 and 9, this computation is very small.
7. Multiply the two modular components together.
This produces:
a^(10x+d) mod 1337
which becomes the updated running answer. 8. Continue until all digits are processed.
After the final digit, the running answer equals:
a^b mod 1337
Why it works
The algorithm relies on the invariant that after processing the first i digits of b, the variable result equals:
a^(value represented by first i digits) mod 1337
Each new digit transforms exponent x into 10x + d. Using exponent laws and modular arithmetic, we correctly update the result without ever constructing the enormous exponent explicitly.
Python Solution
from typing import List
class Solution:
def superPow(self, a: int, b: List[int]) -> int:
MOD = 1337
def mod_pow(base: int, exponent: int) -> int:
result = 1
base %= MOD
while exponent > 0:
if exponent % 2 == 1:
result = (result * base) % MOD
base = (base * base) % MOD
exponent //= 2
return result
result = 1
a %= MOD
for digit in b:
result = (
mod_pow(result, 10) * mod_pow(a, digit)
) % MOD
return result
The implementation begins with a helper function mod_pow, which performs fast modular exponentiation using binary exponentiation. Instead of multiplying the base repeatedly, the exponent is halved at every iteration, reducing the complexity from linear to logarithmic.
Inside mod_pow, whenever the current exponent bit is odd, the current base contributes to the final answer. The base is squared after each step because binary exponentiation decomposes powers into powers of two.
The main function initializes result to 1, representing an empty exponent. The value of a is immediately reduced modulo 1337 to keep all future computations small.
For every digit in the exponent array, the algorithm updates the current result using the mathematical identity:
$a^{10x+d} = (a^x)^{10} \cdot a^d$
The expression:
mod_pow(result, 10)
handles the multiplication of the previous exponent by 10, while:
mod_pow(a, digit)
adds the new digit contribution.
The final result is returned after all digits are processed.
Go Solution
func superPow(a int, b []int) int {
const MOD = 1337
modPow := func(base int, exponent int) int {
result := 1
base %= MOD
for exponent > 0 {
if exponent%2 == 1 {
result = (result * base) % MOD
}
base = (base * base) % MOD
exponent /= 2
}
return result
}
result := 1
a %= MOD
for _, digit := range b {
result = (modPow(result, 10) * modPow(a, digit)) % MOD
}
return result
}
The Go implementation mirrors the Python logic closely. Since Go does not support nested named functions as conveniently as Python, the helper is implemented as an inline closure assigned to modPow.
Go integers are sufficient here because all intermediate values remain bounded by the modulus 1337. After every multiplication, the modulo operation prevents overflow growth.
The exponent digits are processed using Go's range syntax over the slice.
Worked Examples
Example 1
Input: a = 2, b = [3]
The exponent is simply 3.
| Step | Digit | Current Result Before | Update Formula | Result After |
|---|---|---|---|---|
| Start | - | 1 | Initial value | 1 |
| 1 | 3 | 1 | (1^10 * 2^3) mod 1337 | 8 |
Final answer:
8
Example 2
Input: a = 2, b = [1,0]
The exponent is 10.
| Step | Digit | Current Result Before | Computation | Result After |
|---|---|---|---|---|
| Start | - | 1 | Initial value | 1 |
| 1 | 1 | 1 | (1^10 * 2^1) mod 1337 | 2 |
| 2 | 0 | 2 | (2^10 * 2^0) mod 1337 | 1024 |
Final answer:
1024
Example 3
Input: a = 1, b = [4,3,3,8,5,2]
Since any power of 1 remains 1, every step stays unchanged.
| Step | Digit | Current Result Before | Result After |
|---|---|---|---|
| Start | - | 1 | 1 |
| 1 | 4 | 1 | 1 |
| 2 | 3 | 1 | 1 |
| 3 | 3 | 1 | 1 |
| 4 | 8 | 1 | 1 |
| 5 | 5 | 1 | 1 |
| 6 | 2 | 1 | 1 |
Final answer:
1
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log 10) | Each digit requires two fast exponentiation operations with small exponents |
| Space | O(1) | Only a few integer variables are used |
The exponentiation operations are extremely cheap because the exponents are at most 10 and 9. Therefore, each digit contributes only constant work. Since there are n digits, the overall runtime is linear in the size of the exponent array.
The algorithm uses constant extra memory because no additional data structures proportional to input size are allocated.
Test Cases
from typing import List
class Solution:
def superPow(self, a: int, b: List[int]) -> int:
MOD = 1337
def mod_pow(base: int, exponent: int) -> int:
result = 1
base %= MOD
while exponent > 0:
if exponent % 2 == 1:
result = (result * base) % MOD
base = (base * base) % MOD
exponent //= 2
return result
result = 1
a %= MOD
for digit in b:
result = (
mod_pow(result, 10) * mod_pow(a, digit)
) % MOD
return result
sol = Solution()
assert sol.superPow(2, [3]) == 8 # Basic small exponent
assert sol.superPow(2, [1, 0]) == 1024 # Multi-digit exponent
assert sol.superPow(1, [4, 3, 3, 8, 5, 2]) == 1 # Base equals 1
assert sol.superPow(2147483647, [2]) == pow(2147483647, 2, 1337) # Large base
assert sol.superPow(2, [0]) == 1 # Zero exponent
assert sol.superPow(1337, [1]) == 0 # Base divisible by modulus
assert sol.superPow(1338, [1]) == 1 # Base just above modulus
assert sol.superPow(5, [9, 9, 9]) == pow(5, 999, 1337) # Large exponent
assert sol.superPow(7, [1, 0, 0, 0]) == pow(7, 1000, 1337) # Power with many zeros
assert sol.superPow(2, [2, 0, 0, 0]) == pow(2, 2000, 1337) # Very large exponent
| Test | Why |
|---|---|
a=2, b=[3] |
Verifies simplest normal case |
a=2, b=[1,0] |
Verifies multi-digit exponent handling |
a=1, b=[4,3,3,8,5,2] |
Ensures powers of 1 remain correct |
a=2147483647, b=[2] |
Tests maximum base constraint |
a=2, b=[0] |
Validates zero exponent behavior |
a=1337, b=[1] |
Tests modulus reduction to zero |
a=1338, b=[1] |
Ensures modulo reduction works properly |
a=5, b=[9,9,9] |
Tests very large exponent values |
a=7, b=[1,0,0,0] |
Verifies repeated exponent scaling |
a=2, b=[2,0,0,0] |
Stress test with large exponent |
Edge Cases
One important edge case occurs when the exponent is zero, such as b = [0]. Mathematically:
$a^0 = 1$
A buggy implementation might mishandle this because no multiplication steps contribute to the result. In this solution, result starts at 1, so processing digit 0 naturally preserves the correct value.
Another critical case is when a is divisible by 1337. For example:
a = 1337
After modular reduction:
a % 1337 = 0
Any positive exponent should therefore produce 0. Since the implementation immediately reduces a modulo 1337, this behavior emerges automatically.
A third important edge case is extremely large exponents with thousands of digits. Constructing the exponent explicitly would overflow memory or integer limits in many languages. The implementation avoids this entirely by processing digits incrementally and updating the modular result using exponent decomposition.
Finally, very large base values near 2^31 - 1 could cause overflow if intermediate values grew unchecked. The repeated modulo operations ensure every multiplication remains bounded, keeping computations safe and efficient.