279. 完全平方数

Origin: LeetCode 279

题目描述

给定一个正整数 n,找到若干个完全平方数(1, 4, 9, 16, …)使它们的和等于 n。返回最少需要多少个完全平方数。

示例

Input: n = 12 Output: 3 Explanation: 12 = 4 + 4 + 4

Input: n = 13 Output: 2 Explanation: 13 = 4 + 9

约束

  • 1 <= n <= 10

关键信息

  • 完全平方数可无限次使用 → 完全背包
  • 求最少个数 → 和 HackerRank 第 37 题完全一样的框架
  • dp[i] = min(dp[i], dp[i - jj] + 1),jj 是完全平方数

Resolution

我的解法

function numSquares(n: number): number {
    // dp[i] = min(dp[i -j*j] + 1, dp[i])
    const dp = Array.from({ length: n + 1 }, (v, k) => k)
    for (let i = 1; i <= n; i++) {
        let min = i
        for (let j = 0; j * j <= i; j++) {
            min = Math.min(dp[i - j * j] + 1, min)
        }
        dp[i] = min
    }
    return dp[n]
}

初始化用 k(最多用 i 个 1 凑齐),比 Infinity 更直觉。j 从 0 开始,j=0 时 dp[i]+1 比 dp[i] 大会被 min 忽略,不影响正确性。

标准解法(Infinity 初始化)

function numSquares(n: number): number {
    const dp: number[] = Array.from({ length: n + 1 }, () => Infinity)
    dp[0] = 0
    for (let i = 1; i <= n; i++) {
        for (let j = 1; j * j <= i; j++) {
            dp[i] = Math.min(dp[i], dp[i - j * j] + 1)
        }
    }
    return dp[n]
}

Infinity 初始化更通用,j 从 1 开始语义更清晰。两种初始化都对。

解题思路

完全背包 / 硬币找零,和 HackerRank 第 37 题完全一样的框架。硬币就是完全平方数 1, 4, 9, 16…,target 就是 n。

  • dp[i] = 凑齐 i 所需的最少完全平方数个数
  • dp[0] = 0,其他初始化 Infinity
  • 对每个 i,遍历 j(jj <= i),dp[i] = min(dp[i], dp[i - jj] + 1)

j 从 1 开始,j*j <= i 递增。比如 i=12:j=1(1)、j=2(4)、j=3(9)。dp[12] = min(dp[11]+1, dp[8]+1, dp[3]+1) = min(3+1, 2+1, 3+1) = 3。

复杂度

  • 时间:O(n × √n),内层循环最多 √n 次
  • 空间:O(n)