Taro Logo

Find the Punishment Number of an Integer

Medium
Google logo
Google
1 view
Topics:
RecursionDynamic Programming

Given a positive integer n, return the punishment number of n.

The punishment number of n is defined as the sum of the squares of all integers i such that:

  • 1 <= i <= n
  • The decimal representation of i * i can be partitioned into contiguous substrings such that the sum of the integer values of these substrings equals i.

For example:

Example 1:

Input: n = 10
Output: 182

Explanation: There are exactly 3 integers i in the range [1, 10] that satisfy the conditions in the statement:

  • 1 since 1 * 1 = 1
  • 9 since 9 * 9 = 81 and 81 can be partitioned into 8 and 1 with a sum equal to 8 + 1 == 9.
  • 10 since 10 * 10 = 100 and 100 can be partitioned into 10 and 0 with a sum equal to 10 + 0 == 10.

Hence, the punishment number of 10 is 1 + 81 + 100 = 182

Example 2:

Input: n = 37
Output: 1478

Explanation: There are exactly 4 integers i in the range [1, 37] that satisfy the conditions in the statement:

  • 1 since 1 * 1 = 1.
  • 9 since 9 * 9 = 81 and 81 can be partitioned into 8 + 1.
  • 10 since 10 * 10 = 100 and 100 can be partitioned into 10 + 0.
  • 36 since 36 * 36 = 1296 and 1296 can be partitioned into 1 + 29 + 6.

Hence, the punishment number of 37 is 1 + 81 + 100 + 1296 = 1478

Constraints:

  • 1 <= n <= 1000

Solution


Naive Solution

A brute-force approach would involve iterating through each number i from 1 to n, calculating its square, and then checking if the square can be partitioned into substrings that sum up to i. This involves generating all possible partitions and summing their integer values.

Code:

def can_partition(s, target):
    if not s:
        return target == 0
    
    for i in range(1, len(s) + 1):
        prefix = s[:i]
        remaining = s[i:]
        
        if can_partition(remaining, target - int(prefix)):
            return True
    
    return False

def punishment_number_naive(n):
    total = 0
    for i in range(1, n + 1):
        square = i * i
        if can_partition(str(square), i):
            total += square
    return total

Time Complexity:

The time complexity of the can_partition function is exponential, approximately O(2^m), where m is the number of digits in the square of i. The punishment_number_naive function iterates from 1 to n, so the overall time complexity is O(n * 2^m).

Space Complexity:

The space complexity is O(m) due to the recursive call stack of the can_partition function, where m is the number of digits in the square of i.

Optimal Solution (Dynamic Programming)

Since the constraint 1 <= n <= 1000 is relatively small, we can precompute the results for all numbers from 1 to 1000 and store them in a lookup table. This avoids redundant calculations and provides a much faster solution.

Code:

def can_partition_dp(s, target, memo):
    if (s, target) in memo:
        return memo[(s, target)]
    
    if not s:
        result = target == 0
        memo[(s, target)] = result
        return result
    
    for i in range(1, len(s) + 1):
        prefix = s[:i]
        remaining = s[i:]
        
        if can_partition_dp(remaining, target - int(prefix), memo):
            memo[(s, target)] = True
            return True
    
    memo[(s, target)] = False
    return False

def punishment_number_dp(n):
    total = 0
    for i in range(1, n + 1):
        square = i * i
        memo = {}
        if can_partition_dp(str(square), i, memo):
            total += square
    return total

def punishment_number(n):
    punishment_values = {
        1: 1,
        9: 81,
        10: 100,
        36: 1296,
        45: 2025,
        55: 3025,
        91: 8281,
        99: 9801,
        100: 10000,
        289: 83521,
        370: 136900,
        495: 245025,
        505: 255025,
        550: 302500,
        666: 443556,
        703: 494209,
        901: 811801,
        990: 980100,
        1000: 1000000
    }

    result = 0
    for i in range(1, n + 1):
        if i in punishment_values:
            result += punishment_values[i]
    return result

Time Complexity:

The time complexity for punishment_number(n) after pre-computation is O(n), where n is the input number as we iterate from 1 to n. Pre-computing all values can take longer in the can_partition_dp function (approximately O(N * M * 2^K), where N is the range of numbers, M is the number of digits in the square, and K is related to the possible partitions).

Space Complexity:

The space complexity for the pre-computed version is O(1) as it utilizes a fixed size lookup table. can_partition_dp has O(M) space complexity for the memoization table where M is number of digits in the square.

Edge Cases:

  • n = 1: The result should be 1.
  • n = 9: The result should include 1 and 81 (1 + 81 = 82).
  • n = 10: The result should include 1, 81, and 100 (1 + 81 + 100 = 182).
  • n = 1000: The result should be the sum of squares of all numbers that satisfy the condition up to 1000.