2787. Ways to Express an Integer as Sum of Powers
Problem Description
You are given two positive integers n
and x
. Your task is to find the number of ways to express n
as the sum of x
-th powers of unique positive integers.
In other words, you need to count how many different sets of unique positive integers [n₁, n₂, ..., nₖ]
exist such that:
n = n₁ˣ + n₂ˣ + ... + nₖˣ
- All integers in the set are unique (no duplicates allowed)
For example, if n = 160
and x = 3
, one valid way to express 160 is:
160 = 2³ + 3³ + 5³ = 8 + 27 + 125
Since the result can be very large, you should return the answer modulo 10⁹ + 7
.
The solution uses dynamic programming where f[i][j]
represents the number of ways to select numbers from the first i
positive integers such that the sum of their x
-th powers equals j
. For each positive integer i
, you can either include it in your sum (adding iˣ
to the total) or exclude it. The state transition is:
f[i][j] = f[i-1][j] + f[i-1][j-iˣ]
(where the second term is only added if j ≥ iˣ
)
The final answer is f[n][n]
, representing all possible ways using integers from 1 to n to sum up to n.
Intuition
This problem is essentially asking us to find combinations of numbers whose x
-th powers sum to n
. The key insight is that this is a variant of the classic "subset sum" problem, where instead of summing the numbers directly, we're summing their x
-th powers.
Think of it like having a collection of building blocks where each block i
has a value of iˣ
. We want to know how many different ways we can select blocks (without using the same block twice) to reach exactly the target value n
.
The natural approach is to consider each possible integer from 1 to n one by one and make a decision: should we include this number in our sum or not? This "include or exclude" pattern immediately suggests dynamic programming.
Why do we only need to consider integers from 1 to n? Because if any integer i > n
, then iˣ > n
(since x ≥ 1
), making it impossible to use in our sum without exceeding the target.
The dynamic programming state f[i][j]
naturally emerges when we think about building up our solution incrementally:
- The first dimension
i
represents "we've considered integers from 1 to i" - The second dimension
j
represents "we want to achieve a sum of j"
For each integer i
, we face a choice:
- Don't use
i
: Then we need to achieve sumj
using only integers from 1 toi-1
, which isf[i-1][j]
- Use
i
: Then we need to achieve sumj - iˣ
using integers from 1 toi-1
, which isf[i-1][j - iˣ]
This recursive structure naturally leads to our dynamic programming solution, where we build up from smaller subproblems to larger ones, ultimately finding f[n][n]
- the number of ways to achieve sum n
using any integers from 1 to n.
Learn more about Dynamic Programming patterns.
Solution Approach
We implement the solution using a 2D dynamic programming table f[i][j]
where:
i
represents considering integers from 1 toi
j
represents the target sum we want to achieve
Initialization:
- Create a 2D array
f
of size(n+1) × (n+1)
initialized with zeros - Set
f[0][0] = 1
as the base case (there's exactly one way to achieve sum 0 using no numbers: select nothing)
State Transition:
For each integer i
from 1 to n
:
- First, calculate
k = i^x
(thex
-th power ofi
) - For each possible sum
j
from 0 ton
:- Start with not including
i
:f[i][j] = f[i-1][j]
- If including
i
is valid (i.e.,k ≤ j
), add the ways to achievej - k
using integers 1 toi-1
:f[i][j] = (f[i][j] + f[i-1][j - k]) % mod
- Start with not including
Mathematical Formula:
f[i][j] = f[i-1][j] + (j ≥ i^x ? f[i-1][j - i^x] : 0)
Implementation Details:
- We use modulo
10^9 + 7
at each addition to prevent integer overflow - The outer loop iterates through each integer
i
from 1 ton
- For each
i
, we computepow(i, x)
once and reuse it - The inner loop considers all possible sums from 0 to
n
- We only add
f[i-1][j - k]
whenk ≤ j
to ensure valid array access
Time Complexity: O(n² × log(x))
where the log(x)
factor comes from computing i^x
Space Complexity: O(n²)
for the 2D DP table
The final answer is f[n][n]
, representing the number of ways to express n
as the sum of x
-th powers using any combination of unique integers from 1 to n
.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's work through a small example with n = 10
and x = 2
(finding ways to express 10 as sum of squares).
Step 1: Setup
- We need to find ways to express 10 as sum of squares of unique positive integers
- Possible squares to consider: 1² = 1, 2² = 4, 3² = 9, 4² = 16 (too large)
- So we only consider integers 1, 2, and 3
Step 2: Initialize DP Table
Create f[4][11]
(since we consider integers 0 to 3, and sums 0 to 10)
- Set
f[0][0] = 1
(one way to make 0 using no numbers) - All other cells start at 0
Step 3: Fill DP Table
Consider i = 1 (power = 1² = 1):
- For j = 0:
f[1][0] = f[0][0] = 1
(don't use 1) - For j = 1:
f[1][1] = f[0][1] + f[0][0] = 0 + 1 = 1
(use 1) - For j = 2:
f[1][2] = f[0][2] + f[0][1] = 0 + 0 = 0
- ...continuing for all j up to 10
Consider i = 2 (power = 2² = 4):
- For j = 0:
f[2][0] = f[1][0] = 1
- For j = 1:
f[2][1] = f[1][1] = 1
(can't use 2² = 4) - For j = 4:
f[2][4] = f[1][4] + f[1][0] = 0 + 1 = 1
(use 2) - For j = 5:
f[2][5] = f[1][5] + f[1][1] = 1 + 1 = 2
(with or without 2) - ...continuing for all j
Consider i = 3 (power = 3² = 9):
- For j = 9:
f[3][9] = f[2][9] + f[2][0] = 0 + 1 = 1
(use only 3) - For j = 10:
f[3][10] = f[2][10] + f[2][1] = 1 + 1 = 2
Step 4: Extract Answer
f[3][10] = 2
, but we need f[n][n] = f[10][10]
Actually, let me recalculate considering all integers up to 10:
- After processing all integers 1 through 10 (though only 1, 2, 3 have squares ≤ 10)
- The valid ways are:
- 10 = 1² + 3² = 1 + 9
- No other valid combination sums to exactly 10
Therefore, f[10][10] = 1
(there's exactly 1 way to express 10 as sum of squares).
Solution Implementation
1class Solution:
2 def numberOfWays(self, n: int, x: int) -> int:
3 """
4 Count the number of ways to express n as a sum of unique x-th powers.
5
6 Args:
7 n: The target sum to achieve
8 x: The power to which numbers are raised
9
10 Returns:
11 Number of ways to express n as sum of unique x-th powers modulo 10^9 + 7
12 """
13 MOD = 10**9 + 7
14
15 # dp[i][j] represents number of ways to sum to j using first i positive integers raised to power x
16 # i: considering numbers from 1 to i
17 # j: target sum value
18 dp = [[0] * (n + 1) for _ in range(n + 1)]
19
20 # Base case: one way to make sum 0 using 0 numbers (select nothing)
21 dp[0][0] = 1
22
23 # Iterate through each number from 1 to n
24 for num in range(1, n + 1):
25 # Calculate the x-th power of current number
26 power_value = pow(num, x)
27
28 # For each possible sum from 0 to n
29 for target_sum in range(n + 1):
30 # Option 1: Don't include current number's power in the sum
31 dp[num][target_sum] = dp[num - 1][target_sum]
32
33 # Option 2: Include current number's power if it doesn't exceed target
34 if power_value <= target_sum:
35 dp[num][target_sum] = (dp[num][target_sum] +
36 dp[num - 1][target_sum - power_value]) % MOD
37
38 # Return number of ways to form sum n using numbers 1 to n
39 return dp[n][n]
40
1class Solution {
2 public int numberOfWays(int n, int x) {
3 // Define modulo constant for preventing integer overflow
4 final int MOD = (int) 1e9 + 7;
5
6 // dp[i][j] represents the number of ways to form sum j using first i numbers
7 // where each number k contributes k^x to the sum
8 int[][] dp = new int[n + 1][n + 1];
9
10 // Base case: there's one way to form sum 0 using 0 numbers (empty set)
11 dp[0][0] = 1;
12
13 // Iterate through each number from 1 to n
14 for (int currentNumber = 1; currentNumber <= n; ++currentNumber) {
15 // Calculate the contribution of current number (currentNumber^x)
16 long powerValue = (long) Math.pow(currentNumber, x);
17
18 // Update dp table for all possible sums from 0 to n
19 for (int targetSum = 0; targetSum <= n; ++targetSum) {
20 // Option 1: Don't include current number in the sum
21 dp[currentNumber][targetSum] = dp[currentNumber - 1][targetSum];
22
23 // Option 2: Include current number if its power value doesn't exceed target sum
24 if (powerValue <= targetSum) {
25 // Add the ways from previous state after including current number
26 dp[currentNumber][targetSum] = (dp[currentNumber][targetSum] +
27 dp[currentNumber - 1][targetSum - (int) powerValue]) % MOD;
28 }
29 }
30 }
31
32 // Return the number of ways to form sum n using numbers from 1 to n
33 return dp[n][n];
34 }
35}
36
1class Solution {
2public:
3 int numberOfWays(int n, int x) {
4 const int MOD = 1e9 + 7;
5
6 // dp[i][j] represents the number of ways to form sum j using numbers from 1 to i
7 // where each number k contributes k^x to the sum
8 int dp[n + 1][n + 1];
9 memset(dp, 0, sizeof(dp));
10
11 // Base case: there's one way to form sum 0 using no numbers
12 dp[0][0] = 1;
13
14 // Iterate through each number from 1 to n
15 for (int currentNumber = 1; currentNumber <= n; ++currentNumber) {
16 // Calculate the x-th power of the current number
17 long long powerValue = static_cast<long long>(pow(currentNumber, x));
18
19 // For each possible sum from 0 to n
20 for (int targetSum = 0; targetSum <= n; ++targetSum) {
21 // Option 1: Don't include the current number
22 dp[currentNumber][targetSum] = dp[currentNumber - 1][targetSum];
23
24 // Option 2: Include the current number if its power doesn't exceed the target sum
25 if (powerValue <= targetSum) {
26 dp[currentNumber][targetSum] = (dp[currentNumber][targetSum] +
27 dp[currentNumber - 1][targetSum - powerValue]) % MOD;
28 }
29 }
30 }
31
32 // Return the number of ways to form sum n using numbers from 1 to n
33 return dp[n][n];
34 }
35};
36
1/**
2 * Calculates the number of ways to express n as the sum of unique positive integers raised to the power x
3 * @param n - The target sum to achieve
4 * @param x - The power to which each number is raised
5 * @returns The number of ways to express n as sum of unique powers modulo 10^9 + 7
6 */
7function numberOfWays(n: number, x: number): number {
8 const MOD: number = 10 ** 9 + 7;
9
10 // dp[i][j] represents the number of ways to form sum j using numbers from 1 to i
11 // where each number is raised to power x
12 const dp: number[][] = Array.from({ length: n + 1 }, () => Array(n + 1).fill(0));
13
14 // Base case: there's one way to form sum 0 (using no numbers)
15 dp[0][0] = 1;
16
17 // Iterate through each number from 1 to n
18 for (let currentNumber: number = 1; currentNumber <= n; ++currentNumber) {
19 // Calculate the current number raised to power x
20 const currentPower: number = Math.pow(currentNumber, x);
21
22 // Update dp table for all possible sums
23 for (let targetSum: number = 0; targetSum <= n; ++targetSum) {
24 // Case 1: Don't include the current number
25 dp[currentNumber][targetSum] = dp[currentNumber - 1][targetSum];
26
27 // Case 2: Include the current number if it doesn't exceed the target sum
28 if (currentPower <= targetSum) {
29 dp[currentNumber][targetSum] = (dp[currentNumber][targetSum] +
30 dp[currentNumber - 1][targetSum - currentPower]) % MOD;
31 }
32 }
33 }
34
35 // Return the number of ways to form sum n using numbers from 1 to n
36 return dp[n][n];
37}
38
Time and Space Complexity
The time complexity is O(n^2)
, where n
is the given integer in the problem. This is because we have two nested loops: the outer loop iterates from 1
to n
(n iterations), and the inner loop iterates from 0
to n
(n+1 iterations). Each iteration performs constant time operations including the power calculation pow(i, x)
, array access, and modular arithmetic. Therefore, the total time complexity is O(n × n) = O(n^2)
.
The space complexity is O(n^2)
, where n
is the given integer in the problem. This is due to the 2D DP array f
which has dimensions (n+1) × (n+1)
, requiring O(n^2)
space to store all the intermediate states. The array f[i][j]
represents the number of ways to express value j
using the first i
positive integers raised to the power of x
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow When Computing Powers
The most critical pitfall is computing pow(num, x)
without considering overflow. When num
and x
are large, num^x
can exceed the target sum n
by a huge margin, wasting computation and potentially causing issues.
Problem Example:
- If
n = 100
andx = 10
, computing10^10 = 10,000,000,000
is unnecessary since it far exceeds our target. - This leads to unnecessary iterations and memory usage.
Solution: Add an early termination check:
for num in range(1, n + 1):
power_value = pow(num, x)
# Early termination: if power exceeds n, all subsequent powers will too
if power_value > n:
break
for target_sum in range(n + 1):
# ... rest of the logic
2. Space Optimization Overlooked
The current solution uses O(n²) space with a 2D array, but since each row only depends on the previous row, we can optimize to O(n) space.
Optimized Solution:
def numberOfWays(self, n: int, x: int) -> int:
MOD = 10**9 + 7
# Use 1D array instead of 2D
dp = [0] * (n + 1)
dp[0] = 1
num = 1
while True:
power_value = pow(num, x)
if power_value > n:
break
# Traverse backwards to avoid using updated values
for target_sum in range(n, power_value - 1, -1):
dp[target_sum] = (dp[target_sum] + dp[target_sum - power_value]) % MOD
num += 1
return dp[n]
3. Incorrect Loop Bounds
A common mistake is iterating through all numbers from 1 to n even when smaller numbers would suffice. For example, if x = 2
and n = 100
, we only need to check numbers up to 10 (since 10² = 100
).
Better Approach: Calculate the maximum useful number beforehand:
max_num = int(n ** (1/x)) + 1 # Add 1 for safety due to floating point precision
for num in range(1, min(max_num + 1, n + 1)):
# ... rest of the logic
4. Forgetting Modulo Operations
While the provided code handles this correctly, a common mistake is forgetting to apply modulo at every addition, which can cause integer overflow in languages with fixed integer sizes.
Key Points:
- Always apply modulo after additions:
(a + b) % MOD
- Don't apply modulo to intermediate power calculations unless necessary
- Be consistent with modulo application throughout the solution
Which data structure is used in a depth first search?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!