LeetCode-279:完全平方数,用“最短路思维”写出最稳的 DP

本题在线练习:LeetCode 279. 完全平方数 - 在线练习(免费 · 无需登录 · AI 辅助)(https://onefly.top/zero2Leetcode/playground.html?id=279)

配套刷题网站 Zero2Leetcode - 内置本地 OJ + AI 教练,零门槛开刷 Hot 100。

题目概述

给定一个正整数 n,要求用若干个完全平方数(如 1, 4, 9, 16, ...)相加得到 n,并返回所需的最少平方数个数。

例如:

  • n = 12,可以是 4 + 4 + 4,最少是 3
  • n = 13,可以是 4 + 9,最少是 2

核心思路:把它当作“最少步数”的 DP

这题问的是“最少个数”,很像最短路/最少步数:

从 0 走到 n,每一步可以加上一个平方数 s,代价是 +1 步。最少步数就是答案。

用 DP 表达就是:

  • dp[x]:凑出和为 x 的最少平方数个数

初始化:

  • dp[0] = 0
  • 其他先设成一个很大的数

转移:

对每个 x,尝试最后一步选择一个平方数 s = k*k(且 s <= x):

dp[x] = min(dp[x], dp[x - s] + 1)

代码实现(经典一维 DP)

class Solution:
    def numSquares(self, n: int) -> int:
        squares = []
        k = 1
        while k * k <= n:
            squares.append(k * k)
            k += 1

        INF = 10**9
        dp = [INF] * (n + 1)
        dp[0] = 0

        for x in range(1, n + 1):
            for s in squares:
                if s > x:
                    break
                dp[x] = min(dp[x], dp[x - s] + 1)

        return dp[n]

逐行拆解

  1. 先把 <= n 的平方数列出来,这样内层循环不需要每次重新算平方。
  2. dp[x] 表示“最少用几块砖(平方数)拼出长度 x”。
  3. 计算 dp[x] 时,枚举最后放的那块砖 s,剩下的长度是 x - s:
    • 如果 dp[x - s] 已经是最少块数,那么 dp[x - s] + 1 是一种可行拼法
    • 对所有 s 取最小即可

手动模拟(n = 12)

平方数集合是 [1, 4, 9]。

  • dp[1] = dp[0] + 1 = 1
  • dp[4] = min(dp[3]+1, dp[0]+1) = 1(直接用 4)
  • dp[8] = min(dp[7]+1, dp[4]+1) = 2(4+4)
  • dp[12] = min(dp[11]+1, dp[8]+1, dp[3]+1) = 3(4+4+4)

复杂度分析

设 m = floor(sqrt(n)),平方数个数是 m:

  • 时间复杂度:O(n * sqrt(n))
  • 空间复杂度:O(n)

总结

这题最重要的是抓住“最少个数”这个关键词,然后把问题固定成一个标准模型:

dp[x] = 凑到 x 的最少步数,最后一步枚举一个平方数。

一旦写出 dp[x] = min(dp[x - k^2] + 1),实现就非常直接,而且这一套思路还能复用到很多“最少硬币/最少步数”的题里。

posted @ 2026-03-27 01:17  孤飞  阅读(53)  评论(0)    收藏  举报