2698. 求一个整数的惩罚数
2698. 求一个整数的惩罚数
题目
Given a positive integer n, return thepunishment 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 * ican be partitioned into contiguous substrings such that the sum of the integer values of these substrings equalsi.
Example 1:
Input: n = 10
Output: 182
Explanation: There are exactly 3 integers i 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.
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 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
题目大意
给你一个正整数 n ,请你返回 n 的 惩罚数 。
n 的 惩罚数 定义为所有满足以下条件 i 的数的平方和:
1 <= i <= ni * i的十进制表示的字符串可以分割成若干连续子字符串,且这些子字符串对应的整数值之和等于i。
示例 1:
输入: n = 10
输出: 182
解释: 总共有 3 个整数 i 满足要求:
- 1 ,因为 1 * 1 = 1
- 9 ,因为 9 * 9 = 81 ,且 81 可以分割成 8 + 1 。
- 10 ,因为 10 * 10 = 100 ,且 100 可以分割成 10 + 0 。
因此,10 的惩罚数为 1 + 81 + 100 = 182
示例 2:
输入: n = 37
输出: 1478
解释: 总共有 4 个整数 i 满足要求:
- 1 ,因为 1 * 1 = 1
- 9 ,因为 9 * 9 = 81 ,且 81 可以分割成 8 + 1 。
- 10 ,因为 10 * 10 = 100 ,且 100 可以分割成 10 + 0 。
- 36 ,因为 36 * 36 = 1296 ,且 1296 可以分割成 1 + 29 + 6 。
因此,37 的惩罚数为 1 + 81 + 100 + 1296 = 1478
提示:
1 <= n <= 1000
解题思路
遍历
1到n,计算i^2- 对于
1 ≤ i ≤ n,计算i^2并将其转换为字符串str。 - 例如,若
i = 10,则i^2 = 100,转换为字符串"100"。
- 对于
递归检查
i^2是否能拆分成多个部分,使其和等于i- 使用递归函数
check(str, target)来判断str是否可以拆分成多个部分,使得这些部分的数值之和等于target。 - 在
check过程中,使用cache记忆化存储,减少重复计算。 check(str, target)的递归逻辑:- 终止条件:
- 若
str为空且target == 0,返回true(说明成功拆分)。 - 若
target < 0,返回false(说明当前路径不可能满足条件)。
- 若
- 尝试不同的拆分方式:
- 遍历
str的前缀部分str[0:i],将其转换为数字left。 - 递归检查
str[i:]是否可以构成剩余的target - left。 - 若找到满足条件的拆分,则返回
true,否则继续尝试。
- 遍历
- 终止条件:
- 使用递归函数
如果
i^2满足条件,则累加到结果中- 若
check(square.toString(), i)结果为true,则将square加入最终结果。
- 若
返回所有满足条件的
i^2之和
复杂度分析
时间复杂度:
O(n * 2^m),其中m ≈ log(n^2) = 2 log(n)。- 遍历
1到n需要 O(n)。 check(str, target)递归地尝试所有可能的拆分方式,最坏情况下接近 O(2^m)(m为i^2的字符串长度)。
- 遍历
空间复杂度:
O(n log(n))。记忆化哈希表
cache:O(n log(n))- 每次调用
check(str, target, cache),最多存储O(m * i)个子问题的解。 - 其中
m是i^2的位数,i是target的取值范围(最多O(n))。 - 由于
m ≈ 2 log(n),i ≤ n,所以cache的大小最多O(n log(n))。
- 每次调用
递归调用栈空间:
O(log(n))- 在
check()递归时,每次拆分字符串square.toString(),深度最多O(m)(即2 log(n))。 - 由于剪枝优化,实际递归深度通常比
m更浅。 - 最坏情况下,递归调用栈空间为
O(log(n))。
- 在
总的空间复杂度:
O(n log(n)),cache是主要的空间开销,递归调用栈的影响较小。
代码
/**
* @param {number} n
* @return {number}
*/
var punishmentNumber = function (n) {
// 递归检查是否可以拆分
const check = (str, target, cache) => {
// 如果字符串为空且目标值变为0,说明找到一种合法拆分方式
if (str == '' && target == 0) return true;
// 如果目标值小于0,则不可能满足条件
if (target < 0) return false;
// 命中缓存
if (cache.has(str + ',' + target)) return cache.get(str + ',' + target);
let res = false;
// 遍历前缀,尝试不同的拆分方式
for (let i = 0; i < str.length; i++) {
let left = str.substring(0, i + 1); // 当前前缀
let right = str.substring(i + 1); // 剩余部分
// 递归检查剩余部分能否构成 target - left
if (check(right, target - Number(left), cache)) {
cache.set(str + ',' + target, true);
res = true;
break; // 一旦找到满足条件的拆分方式就退出循环
}
}
cache.set(str + ',' + target, false);
return res;
};
let res = 0;
// 遍历 1 到 n,检查哪些 i^2 满足拆分条件
for (let i = 1; i <= n; i++) {
let cache = new Map();
const square = i * i; // 计算 i^2
if (check(square.toString(), i, cache)) {
// 检查是否满足拆分条件
res += square; // 计入最终结果
}
}
return res;
};
相关题目
| 题号 | 标题 | 题解 | 标签 | 难度 | 力扣 |
|---|---|---|---|---|---|
| 2518 | 好分区的数目 | 数组 动态规划 | 🔴 | 🀄️ 🔗 |
