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
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:
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:
Hence, the punishment number of 37 is 1 + 81 + 100 + 1296 = 1478
Constraints:
1 <= n <= 1000
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.
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
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).
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
.
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.
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
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).
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.