在问 · 算法题解

279. 完全平方数

中等动态规划完全背包动态规划力扣原题 ↗

题目描述

如果一个整数等于某个整数的平方,就称它是完全平方数,例如 1、4、9、16;而 3、11 不是。

输入一个正整数 n。要把 n 写成若干个完全平方数之和,同一个平方数可以使用多次。返回所用平方数个数的最小值。

示例 1
输入:n = 12
输出:3

解释:12 = 4 + 4 + 4,至少需要 3 个。

示例 2
输入:n = 13
输出:2

解释:13 = 4 + 9,至少需要 2 个。

数据范围

  • 1 <= n <= 10^4

解题思路:完全背包动态规划

dp[x] 表示凑出 x 的最少个数,枚举最后添加的平方数。

为什么正确

计算 dp[x] 时,所有 dp[x-square] 已经是最优值。

复杂度分析

时间 O(n√n),空间 O(n)。

易错点

完全平方数可以重复使用,所以每个 x 都应从更小的 dp 状态转移。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def numSquares(self, n: int) -> int:
        dp = [0] + [n + 1] * n
        for x in range(1, n + 1):
            square = 1
            while square * square <= x:
                dp[x] = min(dp[x], dp[x - square * square] + 1)
                square += 1
        return dp[n]

载入编辑器,自己跑一遍 →

其他解法

动态规划和 BFS 都是通用的“最少个数”解法:DP 按金额顺序填表,BFS 按个数分层扩展并在到达 0 时提前停止。四平方和定理直接把答案限定在 1–4,逐一判断,最快但只适用于平方数。

解法 2:广度优先搜索 时间 O(n√n),空间 O(n)

把 0 到 n 的每个整数看成图上的点,从 x 减去一个完全平方数得到的数之间连一条边。从 n 出发做 BFS,第一次到达 0 时的层数就是最少的平方数个数。已访问的数不再入队。

把“最少个数”看成无权图最短路,答案很小(最多 4)时只搜索很少几层,实际通常比 DP 快。需要队列和访问数组。

class Solution:
    def numSquares(self, n: int) -> int:
        squares = []
        i = 1
        while i * i <= n:
            squares.append(i * i)
            i += 1
        visited = [False] * (n + 1)
        visited[n] = True
        level = [n]
        steps = 0
        while level:
            steps += 1
            nxt = []
            for x in level:
                for sq in squares:
                    y = x - sq
                    if y < 0:
                        break
                    if y == 0:
                        return steps
                    if not visited[y]:
                        visited[y] = True
                        nxt.append(y)
            level = nxt
        return steps

解法 3:四平方和定理 时间 O(√n),空间 O(1)

拉格朗日四平方和定理:任何正整数都能表示成至多 4 个平方数之和。勒让德三平方和定理:n 需要 4 个当且仅当 n = 4^a(8b+7)。依次判断:n 是平方数返回 1;满足 4^a(8b+7) 返回 4;枚举 i 看 n - i² 是否为平方数,是则返回 2;否则返回 3。

最快,只要 O(√n) 且不占额外空间。依赖数论定理,面试中难以现场推出,也不能推广到其他“最少硬币”类问题。

class Solution:
    def numSquares(self, n: int) -> int:
        def is_square(x):
            r = int(x ** 0.5)
            while r * r > x:
                r -= 1
            while (r + 1) * (r + 1) <= x:
                r += 1
            return r * r == x

        if is_square(n):
            return 1
        m = n
        while m % 4 == 0:
            m //= 4
        if m % 8 == 7:
            return 4
        i = 1
        while i * i < n:
            if is_square(n - i * i):
                return 2
            i += 1
        return 3

动态规划 专题的其他题目

查看 动态规划 全部题目 →