求一个整数的惩罚数 - 回溯算法
题目描述
给定一个正整数 n,请你返回 n 的惩罚数。
惩罚数 的定义如下:对于满足以下条件的所有 i:
[1 \leq i \leq n]
[i \times i] 的十进制表示的字符串可以分割成若干连续子字符串,且这些子字符串对应的整数值之和等于 (i)。
示例
示例 1:
输入:n = 10
输出:182
解释:总共有 3 个整数 (i) 满足要求:
- (1) ,因为 (1 \times 1 = 1)
- (9) ,因为 (9 \times 9 = 81) ,且 (81) 可以分割成 (8 + 1)。
- (10) ,因为 (10 \times 10 = 100) ,且 (100) 可以分割成 (10 + 0)。
因此,10 的惩罚数为 (1 + 81 + 100 = 182)
示例 2:
输入:n = 37
输出:1478
解释:总共有 4 个整数 (i) 满足要求:
- (1) ,因为 (1 \times 1 = 1)
- (9) ,因为 (9 \times 9 = 81) ,且 (81) 可以分割成 (8 + 1)。
- (10) ,因为 (10 \times 10 = 100) ,且 (100) 可以分割成 (10 + 0)。
- (36) ,因为 (36 \times 36 = 1296) ,且 (1296) 可以分割成 (1 + 29 + 6)。
因此,37 的惩罚数为 (1 + 81 + 100 + 1296 = 1478)
提示
[1 \leq n \leq 1000]
代码解析
#include <functional>
using namespace std;
// n的惩罚数:
// 取值范围[1, n]
// i * i的字符串表示中,可以分割为总和为i的整数子串
// 朴素做法是暴力搜索[1, n],平方后检查其各个子串的组合结果。
// 数据量范围小且简单,可以打表和暴力。
class Solution {
public:
int punishmentNumber(int n) {
function<bool(int, int, int, int, int)> check = [&](int ori_num, int sq_num, int start, int sum, int digit_end) {
if (start > digit_end) {
return sum == ori_num;
}
int curr = 0;
// 使用数学的模算法,通过直接计算的方法获取子串的值。
// 这样可以减少构建字符串的开销。
for (int i = start; i <= digit_end; i *= 10) {
int digit = (sq_num % i) / (start / 10);
curr = digit;
if (check(ori_num, sq_num, i * 10, sum + curr, digit_end)) {
return true;
}
}
return false;
};
int ans = 0;
for (int i = 1; i <= n; i++) {
int sq = i * i;
int get_digit_end = 1;
int temp = sq;
while (temp) {
temp /= 10;
get_digit_end *= 10;
}
if (check(i, sq, 10, 0, get_digit_end)) {
ans += sq;
}
}
return ans;
}
};
代码详解
这段 C++ 代码实现了求一个整数的惩罚数的功能,采用了回溯算法的思想。下面是对代码的详细解析:
-
函数定义和注释:
punishmentNumber
函数接受一个正整数n
作为参数,返回n
的惩罚数。- 注释中说明了问题的本质,即找到满足条件的整数 (i),使得 (i \times i) 的字符串表示可以分割成若干连续子字符串,且这些子字符串对应的整数值之和等于 (i)。
-
回溯函数定义和注释:
check
函数是回溯函数,用于检查对于给定的整数 (i),其平方值 (i \times i) 的字符串表示是否满足条件。ori_num
是原始整数 (i),sq_num
是其平方值。start
表示当前检查的位置,sum
是当前已累积的和,digit_end
表示整数的位数。
-
回溯算法核心思想:
- 通过递归遍历整个数字,利用取余的方法,将其每一位拆分出来,检查是否满足条件。
- 如果已经遍历到最后一位 (
start > digit_end
),则判断当前累积的和是否等于原始整数。 - 在遍历的过程中,通过取余数的方式获取每一位数字。
-
主循环:
- 主循环遍历 [1, n] 的整数,计算其平方值,并调用
check
函数判断是否满足条件。 - 满足条件的平方值累加到最终答案中。
- 主循环遍历 [1, n] 的整数,计算其平方值,并调用
回溯算法详解
回溯算法是一种通过尝试所有可能的解决方案来解决问题的算法。它通过逐步构建解决方案,并在发现当前路径不能达到目标时进行回退,继续尝试其他路径。在解决某些组合问题或遍历问题时,回溯算法是一种非常有效且常用的方法。
核心思想
回溯算法的核心思想是尝试所有可能的选择,当发现当前选择无法达到目标时,回溯到上一步,尝试其他选择。在这个过程中,通过递归实现对所有可能路径的搜索。
伪代码框架
function backtrack(参数) {
if (满足结束条件) {
存储当前解决方案;
return;
}
for (每个可能的选择) {
尝试当前选择;
backtrack(新的参数);
撤销当前选择;
}
}
具体应用 - 求一个整数的惩罚数
问题描述
给定一个正整数 n
,返回其惩罚数。惩罚数的定义是对于满足条件的所有整数 i
:
- (1 \leq i \leq n)
- (i \times i) 的十进制表示的字符串可以分割成若干连续子字符串,且这些子字符串对应的整数值之和等于
i
。
回溯算法实现
function backtrack(ori_num, sq_num, start, sum, digit_end) {
if (start > digit_end) {
if (sum == ori_num) {
检查是否满足条件,并返回结果
return true;
}
return false;
}
for (每个可能的选择) {
尝试当前选择;
backtrack(ori_num, sq_num, 下一个位置, 新的和, digit_end);
撤销当前选择;
}
}
具体步骤
-
定义回溯函数
backtrack
,参数包括原始整数ori_num
,其平方值sq_num
,当前检查的位置start
,当前已累积的和sum
,和整数的位数digit_end
。 -
判断是否达到结束条件,如果是,则检查当前累积的和是否等于原始整数,如果是,则存储当前解决方案。
-
在循环中尝试每一位的可能选择,递归调用
backtrack
。 -
在递归调用之前尝试当前选择,在递归调用之后撤销当前选择,以实现回溯的效果。
主循环
在主循环中,遍历 [1, n] 的整数,计算其平方值,并调用 backtrack
函数开始回溯搜索。将满足条件的平方值累加到最终答案中。
这样,通过回溯算法,可以逐步构建出满足条件的整数,并得到惩罚数的计算结果。
总结
这篇博客详细介绍了回溯算法在解决一个整数惩罚数问题中的应用。通过代码实现和注释,展示了算法的核心思想和实际实现方法。该算法的时间复杂度较低,适用于较小的数据范围。