Facebook Pixel

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 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 . 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 sum j using only integers from 1 to i-1, which is f[i-1][j]
  • Use i: Then we need to achieve sum j - iˣ using integers from 1 to i-1, which is f[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 to i
  • 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:

  1. First, calculate k = i^x (the x-th power of i)
  2. For each possible sum j from 0 to n:
    • 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 achieve j - k using integers 1 to i-1:
      • f[i][j] = (f[i][j] + f[i-1][j - k]) % mod

Mathematical Formula:

f[i][j] = f[i-1][j] + (j ≥ i^x ? f[i-1][j - i^x] : 0)

Implementation Details:

  1. We use modulo 10^9 + 7 at each addition to prevent integer overflow
  2. The outer loop iterates through each integer i from 1 to n
  3. For each i, we compute pow(i, x) once and reuse it
  4. The inner loop considers all possible sums from 0 to n
  5. We only add f[i-1][j - k] when k ≤ 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 Evaluator

Example 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:
    1. 10 = 1² + 3² = 1 + 9
    2. 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 and x = 10, computing 10^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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which data structure is used in a depth first search?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More