求一个整数的惩罚数-LeetCode
2698. 求一个整数的惩罚数 - Find the Punishment Number of an Integer
🎯 问题描述(来源于LeetCode)
描述:
给定一个正整数 n,返回 1 到 n 之间所有惩罚数的平方和。
惩罚数的定义为:若一个整数 i 的平方 i*i 的十进制表示可以分割成若干连续子串,使得这些子串对应的整数值之和等于 i,则 i 是惩罚数。
说明:
- 分割时子串必须连续,且必须使用所有数字。
- 例如
1是惩罚数,因为1²=1可分割为"1",和为1。 - 例如
9是惩罚数,因为9²=81可分割为"8"+"1",和为9。
示例:
- 示例 1:
1 | 输入: n = 10 |
- 示例 2:
1 | 输入: n = 37 |
💻 解题思路
思路:DFS 通过参数模拟分割决策
算法步骤
- 定义辅助函数
can_partition(num),判断num是否为惩罚数:- 将
num * num转为字符串s,长度为N。 - 定义递归函数
dfs(gap, total, cur),其中:gap表示当前已处理的最后一个字符索引(从 0 开始)。total表示已经确定并累加的部分的和(不包括当前正在构建的数字cur)。cur表示当前正在构建的数字(从当前分割的起始字符累加而成)。
- 递归终止:如果
gap == N - 1(已处理到最后一个字符),则判断total + cur == num。 - 否则,取下一个字符
next_char = int(s[gap+1]),尝试两种操作:- 不分割:将下一个字符追加到当前数字
cur上,递归dfs(gap+1, total, cur * 10 + next_char)。 - 分割:将当前数字
cur加入total,并以下一个字符作为新cur开始,递归dfs(gap+1, total + cur, next_char)。
- 不分割:将下一个字符追加到当前数字
- 返回是否找到有效分割。
- 将
- 若字符串
s为空(即num=0),直接返回False。 - 从
dfs(0, 0, int(s[0]))开始搜索。 - 主函数遍历 i=1 到 n,若
can_partition(i)为真,则累加i*i。 - 返回累加结果。
代码实现
1 | from typing import List |
📊 性能分析
- 时间复杂度:每个数字的平方字符串长度约为
2*log10(n),递归分支最多2^{len(s)},但通过剪枝(实际只有两种选择)复杂度仍为指数级,但n较小(通常 ≤ 1000)时可通过。 - 空间复杂度:,递归栈深度。
思考
该解法采用了一种新颖的递归参数设计,通过 (gap, total, cur) 隐式地模拟分割决策,避免了显式枚举子串的循环。
优点:
- 代码简洁,无需循环分割点,递归分支固定为两个(追加或分割)。
- 通过
cur累积当前数字,total存储已确定部分,状态转移清晰。 - 递归终止条件
gap == N-1直接利用已处理字符数,逻辑紧凑。
注意事项:
- 初始调用
dfs(0, 0, int(s[0])),将第一个字符作为初始cur。 - 当
s为空时(num=0,但题目中i从 1 开始,不会出现),直接返回False。 - 与标准回溯(循环枚举所有子串)相比,此方法将每个字符位置的分支简化为“合并”或“分割”两种操作,本质上等价,但实现更加统一。
潜在改进:
- 可加入剪枝:若
cur > num且尚未分割,则后续追加只会更大,可提前返回False,但当前代码未实现,因为num通常较小,不影响。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 笺札!






