【算法思考记录】【C++、回溯、数学技巧】力扣2698. 求一个整数的惩罚数

原题链接


求一个整数的惩罚数 - 回溯算法

题目描述

给定一个正整数 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++ 代码实现了求一个整数的惩罚数的功能,采用了回溯算法的思想。下面是对代码的详细解析:

  1. 函数定义和注释:

    • punishmentNumber 函数接受一个正整数 n 作为参数,返回 n 的惩罚数。
    • 注释中说明了问题的本质,即找到满足条件的整数 (i),使得 (i \times i) 的字符串表示可以分割成若干连续子字符串,且这些子字符串对应的整数值之和等于 (i)。
  2. 回溯函数定义和注释:

    • check 函数是回溯函数,用于检查对于给定的整数 (i),其平方值 (i \times i) 的字符串表示是否满足条件。
    • ori_num 是原始整数 (i),sq_num 是其平方值。
    • start 表示当前检查的位置,sum 是当前已累积的和,digit_end 表示整数的位数。
  3. 回溯算法核心思想:

    • 通过递归遍历整个数字,利用取余的方法,将其每一位拆分出来,检查是否满足条件。
    • 如果已经遍历到最后一位 (start > digit_end),则判断当前累积的和是否等于原始整数。
    • 在遍历的过程中,通过取余数的方式获取每一位数字。
  4. 主循环:

    • 主循环遍历 [1, n] 的整数,计算其平方值,并调用 check 函数判断是否满足条件。
    • 满足条件的平方值累加到最终答案中。

回溯算法详解

回溯算法是一种通过尝试所有可能的解决方案来解决问题的算法。它通过逐步构建解决方案,并在发现当前路径不能达到目标时进行回退,继续尝试其他路径。在解决某些组合问题或遍历问题时,回溯算法是一种非常有效且常用的方法。

核心思想

回溯算法的核心思想是尝试所有可能的选择,当发现当前选择无法达到目标时,回溯到上一步,尝试其他选择。在这个过程中,通过递归实现对所有可能路径的搜索。

伪代码框架

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);
        撤销当前选择;
    }
}

具体步骤

  1. 定义回溯函数 backtrack,参数包括原始整数 ori_num,其平方值 sq_num,当前检查的位置 start,当前已累积的和 sum,和整数的位数 digit_end

  2. 判断是否达到结束条件,如果是,则检查当前累积的和是否等于原始整数,如果是,则存储当前解决方案。

  3. 在循环中尝试每一位的可能选择,递归调用 backtrack

  4. 在递归调用之前尝试当前选择,在递归调用之后撤销当前选择,以实现回溯的效果。

主循环

在主循环中,遍历 [1, n] 的整数,计算其平方值,并调用 backtrack 函数开始回溯搜索。将满足条件的平方值累加到最终答案中。

这样,通过回溯算法,可以逐步构建出满足条件的整数,并得到惩罚数的计算结果。

总结

这篇博客详细介绍了回溯算法在解决一个整数惩罚数问题中的应用。通过代码实现和注释,展示了算法的核心思想和实际实现方法。该算法的时间复杂度较低,适用于较小的数据范围。

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值