【中等】279. 完全平方数

0 阅读1分钟

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,149 和 16 都是完全平方数,而 3 和 11 不是。

示例 1:

输入: n = 12
输出: 3 
解释: 12 = 4 + 4 + 4

示例 2:

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

提示:

  • 1 <= n <= 104

1. 生活案例:装修铺地砖

想象你正在装修一个面积为 nn 的长条形房间。装修市场比较奇怪,他们只卖“正方形地砖”,且边长必须是整数。

  • 可选的地砖:面积分别为 1×1=11 \times 1=12×2=42 \times 2=43×3=93 \times 3=94×4=164 \times 4=16 \dots(这些就是完全平方数)。

  • 你的任务:用最少数量的地砖,刚好把面积为 nn 的地面铺满。

  • 策略

    • 为了铺满面积为 1212 的地面:

      • 方案 A:全用 1×11 \times 1 的,需要 1212 块。
      • 方案 B:用 2×22 \times 2 的(面积为 4),12=4+4+412 = 4 + 4 + 4,需要 3 块。
      • 对比发现方案 B 更省砖!

2. 代码解析与“生活化”注释

这段代码通过不断尝试“最后一块砖用多大”,来反推最少数量。

JavaScript

/**
 * @param {number} n - 房间的总面积
 * @return {number} - 最少需要的地砖数量
 */
var numSquares = function(n) {
    // dp[i] 代表:铺满面积为 i 的房间,最少需要多少块地砖
    // 初始值给 n(全铺 1x1 地砖的情况),这已经是最大可能的数量了
    let dp = new Array(n + 1).fill(n);

    // 基础情况:面积为 0 时,需要 0 块地砖
    dp[0] = 0;
    // 面积为 1 时,只能用 1x1 的,需要 1 块
    dp[1] = 1;

    // i 代表当前我们要计算的“目标面积”,从 1 逐渐算到 n
    for (let i = 1; i <= n; i++) {
        // j 代表地砖的边长,j*j 就是地砖的面积(1, 4, 9, 16...)
        // 我们尝试所有“面积不大于剩余空间”的正方形砖
        for (let j = 1; j * j <= i; j++) {
            // 生活化解释:
            // 我尝试拿出一块面积为 j*j 的正方形砖
            // 那么总砖数 = 铺满剩下面积 (i - j*j) 的最少砖数 + 1 (当前这一块)
            // 我们在各种可能的 j 中,选一个能让 dp[i] 最小的
            dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
        }
    }

    // 返回铺满面积 n 的最少砖数
    return dp[n];
};

3. 为什么代码这样写?(算法关联)

  1. 它就是零钱兑换

    • 在“零钱兑换”里,硬币是 coins = [1, 2, 5]

    • 在这里,硬币是 coins = [1, 4, 9, 16, ...]

    • 逻辑完全一致:

      dp[i]=min(dp[isquare]+1)dp[i] = \min(dp[i - \text{square}] + 1)

  2. 内层循环的边界j * j <= i。这很聪明,因为你不可能用一块面积比房间还大的地砖。

  3. 计算顺序:先算小房间(面积 1, 2, 3...),因为算大房间(面积 12)时,需要用到小房间(比如面积 8)的结果。