没有
字数 2711 2025-12-15 22:16:17

好的,我已经记录了你已学习过的所有题目。现在,我给你随机出一道你列表中没有出现过的、经典的线性动态规划题目。

题目:完全平方数

题目描述:

给定一个正整数 n,你的任务是找到最少数量的完全平方数(例如 1, 4, 9, 16, …)使得它们的和等于 n

换句话说,我们需要用最少数量的完全平方数来拼凑出整数 n。这是一个典型的“数字拆分”或“硬币找零”类问题,只不过这里的“硬币”是所有的完全平方数。

示例 1:
输入:n = 12
输出:3
解释:12 = 4 + 4 + 4。也可以表示为 12 = 9 + 1 + 1 + 1,但需要4个数,不是最少的。最少需要3个。

示例 2:
输入:n = 13
输出:2
解释:13 = 4 + 9


解题思路:循序渐进地讲解

我们可以把这个问题看作一个动态规划问题,状态定义为凑出某个金额 i 所需的最少完全平方数的数量。

步骤 1:定义状态

定义 dp[i] 为:凑出总和为 i 所需的最少完全平方数的个数

我们的最终目标是求 dp[n]

步骤 2:寻找状态转移方程

如何计算 dp[i] 呢?
要凑出总和 i,最后一个加入的完全平方数可能是 1, 4, 9, 16, …,当然,这个数不能大于 i

  • 如果我们最后加入的数是 1(即 1*1),那么我们需要先凑出 i - 1,然后再加1个1。所以方案数为 dp[i-1] + 1
  • 如果我们最后加入的数是 4(即 2*2),那么我们需要先凑出 i - 4,然后再加1个4。所以方案数为 dp[i-4] + 1
  • 如果我们最后加入的数是 9(即 3*3),那么方案数为 dp[i-9] + 1
  • ……

我们需要找到所有可能中数量最少的那个。

因此,状态转移方程为:
dp[i] = min( dp[i - j*j] + 1 ),其中 j*j <= i
即,我们遍历所有小于等于 i 的完全平方数 j*j,看从 i - j*j 这个状态转移过来,哪个需要的数字个数最少。

步骤 3:确定初始条件

  • dp[0] = 0。凑出总和0,不需要任何数。
  • 对于其他 i,我们可以先初始化为一个很大的数(比如 n+1),因为最多的情况就是全部用1来凑,需要 i 个。我们将在状态转移中更新它。

步骤 4:计算顺序

由于计算 dp[i] 时,需要用到 dp[i - j*j](这些值都比 i 小),所以我们按照 i1n 的顺序来计算。

步骤 5:举例说明(以 n=12 为例)

  1. 初始化
    dp = [0, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞],长度为 n+1=13dp[0]=0

  2. 计算 i=1
    小于等于1的完全平方数只有 1*1=1
    dp[1] = dp[1-1] + 1 = dp[0] + 1 = 0 + 1 = 1
    更新 dp = [0, 1, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞]

  3. 计算 i=2
    小于等于2的完全平方数只有 1*1=1
    dp[2] = dp[2-1] + 1 = dp[1] + 1 = 1 + 1 = 2。(即 2 = 1 + 1
    dp = [0, 1, 2, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞]

  4. 计算 i=3
    小于等于3的完全平方数只有 1
    dp[3] = dp[3-1] + 1 = dp[2] + 1 = 2 + 1 = 3。(即 3 = 1 + 1 + 1
    dp = [0, 1, 2, 3, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞]

  5. 计算 i=4
    小于等于4的完全平方数有 14

    • 1dp[4-1] + 1 = dp[3] + 1 = 3 + 1 = 4
    • 4dp[4-4] + 1 = dp[0] + 1 = 0 + 1 = 1
      取最小值 min(4, 1) = 1
      所以 dp[4] = 1。(即 4 = 4
      dp = [0, 1, 2, 3, 1, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞]
  6. 继续这个过程

    • i=5: 检查 14
      min(dp[4]+1=2, dp[1]+1=2) = 2。 (5=4+1
    • i=6: 检查 14
      min(dp[5]+1=3, dp[2]+1=3) = 3。 (6=4+1+1
    • i=7: 检查 14
      min(dp[6]+1=4, dp[3]+1=4) = 4。 (7=4+1+1+1
    • i=8: 检查 14
      min(dp[7]+1=5, dp[4]+1=2) = 2。 (8=4+4
    • i=9: 检查 1, 4, 9
      min(dp[8]+1=3, dp[5]+1=3, dp[0]+1=1) = 1。 (9=9
    • i=10: 检查 1, 4, 9
      min(dp[9]+1=2, dp[6]+1=4, dp[1]+1=2) = 2。 (10=9+1
    • i=11: 检查 1, 4, 9
      min(dp[10]+1=3, dp[7]+1=5, dp[2]+1=3) = 3。 (11=9+1+1
    • i=12: 检查 1, 4, 9
      min(dp[11]+1=4, dp[8]+1=3, dp[3]+1=4) = 3。 (12=4+4+4
  7. 最终结果
    dp[12] = 3,与示例一致。

步骤 6:算法实现(Python伪代码)

def numSquares(n: int) -> int:
    # 初始化dp数组,dp[0]=0,其他为正无穷大
    dp = [float(inf)] * (n + 1)
    dp[0] = 0

    # 遍历所有需要计算的状态 i (从1到n)
    for i in range(1, n + 1):
        # 遍历所有可能的完全平方数 j*j <= i
        j = 1
        while j * j <= i:
            # 状态转移:dp[i] 可能是由 dp[i - j*j] 加上一个平方数 j*j 得来
            dp[i] = min(dp[i], dp[i - j * j] + 1)
            j += 1

    return dp[n]

步骤 7:复杂度分析

  • 时间复杂度O(n * sqrt(n))。外层循环遍历 n 次,内层循环的 j 最大到 sqrt(n)
  • 空间复杂度O(n),用于存储 dp 数组。

总结一下
这道题的核心是将一个“求最值”的优化问题,分解成一系列子问题(dp[i] 表示凑出 i 的最优解)。通过分析最后一个加入的“成分”(一个完全平方数),我们建立了当前问题 dp[i] 与更小子问题 dp[i - j*j] 之间的联系,从而可以用自底向上的动态规划高效求解。

好的,我已经记录了你已学习过的所有题目。现在,我给你随机出一道你列表中 没有 出现过的、经典的线性动态规划题目。 题目:完全平方数 题目描述: 给定一个正整数 n ,你的任务是找到 最少数量的完全平方数 (例如 1, 4, 9, 16, …)使得它们的和等于 n 。 换句话说,我们需要用最少数量的完全平方数来拼凑出整数 n 。这是一个典型的“数字拆分”或“硬币找零”类问题,只不过这里的“硬币”是所有的完全平方数。 示例 1: 输入: n = 12 输出: 3 解释: 12 = 4 + 4 + 4 。也可以表示为 12 = 9 + 1 + 1 + 1 ,但需要4个数,不是最少的。最少需要3个。 示例 2: 输入: n = 13 输出: 2 解释: 13 = 4 + 9 。 解题思路:循序渐进地讲解 我们可以把这个问题看作一个 动态规划 问题,状态定义为凑出某个金额 i 所需的最少完全平方数的数量。 步骤 1:定义状态 定义 dp[i] 为: 凑出总和为 i 所需的最少完全平方数的个数 。 我们的最终目标是求 dp[n] 。 步骤 2:寻找状态转移方程 如何计算 dp[i] 呢? 要凑出总和 i ,最后一个加入的完全平方数可能是 1, 4, 9, 16, … ,当然,这个数不能大于 i 。 如果我们最后加入的数是 1 (即 1*1 ),那么我们需要先凑出 i - 1 ,然后再加1个 1 。所以方案数为 dp[i-1] + 1 。 如果我们最后加入的数是 4 (即 2*2 ),那么我们需要先凑出 i - 4 ,然后再加1个 4 。所以方案数为 dp[i-4] + 1 。 如果我们最后加入的数是 9 (即 3*3 ),那么方案数为 dp[i-9] + 1 。 …… 我们需要找到所有可能中 数量最少 的那个。 因此,状态转移方程为: dp[i] = min( dp[i - j*j] + 1 ) ,其中 j*j <= i 。 即,我们遍历所有小于等于 i 的完全平方数 j*j ,看从 i - j*j 这个状态转移过来,哪个需要的数字个数最少。 步骤 3:确定初始条件 dp[0] = 0 。凑出总和0,不需要任何数。 对于其他 i ,我们可以先初始化为一个很大的数(比如 n+1 ),因为最多的情况就是全部用 1 来凑,需要 i 个。我们将在状态转移中更新它。 步骤 4:计算顺序 由于计算 dp[i] 时,需要用到 dp[i - j*j] (这些值都比 i 小),所以我们按照 i 从 1 到 n 的顺序来计算。 步骤 5:举例说明(以 n=12 为例) 初始化 : dp = [0, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞] ,长度为 n+1=13 。 dp[0]=0 。 计算 i=1 : 小于等于1的完全平方数只有 1*1=1 。 dp[1] = dp[1-1] + 1 = dp[0] + 1 = 0 + 1 = 1 。 更新 dp = [0, 1, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞] 计算 i=2 : 小于等于2的完全平方数只有 1*1=1 。 dp[2] = dp[2-1] + 1 = dp[1] + 1 = 1 + 1 = 2 。(即 2 = 1 + 1 ) dp = [0, 1, 2, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞] 计算 i=3 : 小于等于3的完全平方数只有 1 。 dp[3] = dp[3-1] + 1 = dp[2] + 1 = 2 + 1 = 3 。(即 3 = 1 + 1 + 1 ) dp = [0, 1, 2, 3, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞] 计算 i=4 : 小于等于4的完全平方数有 1 和 4 。 用 1 : dp[4-1] + 1 = dp[3] + 1 = 3 + 1 = 4 用 4 : dp[4-4] + 1 = dp[0] + 1 = 0 + 1 = 1 取最小值 min(4, 1) = 1 。 所以 dp[4] = 1 。(即 4 = 4 ) dp = [0, 1, 2, 3, 1, ∞, ∞, ∞, ∞, ∞, ∞, ∞, ∞] 继续这个过程 : i=5 : 检查 1 和 4 。 min(dp[4]+1=2, dp[1]+1=2) = 2 。 ( 5=4+1 ) i=6 : 检查 1 和 4 。 min(dp[5]+1=3, dp[2]+1=3) = 3 。 ( 6=4+1+1 ) i=7 : 检查 1 和 4 。 min(dp[6]+1=4, dp[3]+1=4) = 4 。 ( 7=4+1+1+1 ) i=8 : 检查 1 和 4 。 min(dp[7]+1=5, dp[4]+1=2) = 2 。 ( 8=4+4 ) i=9 : 检查 1 , 4 , 9 。 min(dp[8]+1=3, dp[5]+1=3, dp[0]+1=1) = 1 。 ( 9=9 ) i=10 : 检查 1 , 4 , 9 。 min(dp[9]+1=2, dp[6]+1=4, dp[1]+1=2) = 2 。 ( 10=9+1 ) i=11 : 检查 1 , 4 , 9 。 min(dp[10]+1=3, dp[7]+1=5, dp[2]+1=3) = 3 。 ( 11=9+1+1 ) i=12 : 检查 1 , 4 , 9 。 min(dp[11]+1=4, dp[8]+1=3, dp[3]+1=4) = 3 。 ( 12=4+4+4 ) 最终结果 : dp[12] = 3 ,与示例一致。 步骤 6:算法实现(Python伪代码) 步骤 7:复杂度分析 时间复杂度 : O(n * sqrt(n)) 。外层循环遍历 n 次,内层循环的 j 最大到 sqrt(n) 。 空间复杂度 : O(n) ,用于存储 dp 数组。 总结一下 : 这道题的核心是将一个“求最值”的优化问题,分解成一系列子问题( dp[i] 表示凑出 i 的最优解)。通过分析最后一个加入的“成分”(一个完全平方数),我们建立了当前问题 dp[i] 与更小子问题 dp[i - j*j] 之间的联系,从而可以用自底向上的动态规划高效求解。