LeetCode Hot100(67/100)——279. 完全平方数

题目链接

题目说明

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

  • 完全平方数:形如 1, 4, 9, 16, ...(即某个整数的平方)
  • 示例:
    • n = 12,答案是 312 = 4 + 4 + 4
    • n = 13,答案是 213 = 4 + 9

约束(LeetCode):

  • 1 <= n <= 10^4

解题思路总览

Perfect Squares

动态规划

状态dp[i]

转移dp[i]=min(dp[i-j*j]+1)

复杂度O(n*sqrt(n))

BFS最短路

节点是剩余值

边是减去一个平方数

层数即使用个数

数学定理

拉格朗日四平方定理

勒让德三平方定理


解法一:动态规划(推荐,通用且好理解)

原理

定义 dp[i]:表示组成整数 i 所需的最少完全平方数个数。
那么:

d p [ i ] = min ⁡ j 2 ≤ i ( d p [ i − j 2 ] + 1 ) dp[i] = \min_{j^2 \le i}(dp[i-j^2] + 1) dp[i]=j2imin(dp[ij2]+1)

含义是:最后一次选了一个平方数 j*j,前面剩下 i-j*j 的最优解再加 1。

状态转移流程图

初始化 dp[0]=0

计算 i=1..n

枚举 j, 满足 j*j <= i

dp[i] = min(dp[i], dp[i-j*j] + 1)

j继续?

i继续?

返回 dp[n]

Java 代码

import java.util.Arrays;

class Solution {
    public int numSquares(int n) {
        int[] dp = new int[n + 1];
        Arrays.fill(dp, Integer.MAX_VALUE);
        dp[0] = 0;

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j * j <= i; j++) {
                dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
            }
        }
        return dp[n];
    }
}

复杂度分析

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

解法二:BFS(把问题看成最短路径)

原理

把每个数字看作图中的一个节点。
n 出发,每次可以减去一个平方数 ss <= 当前值),到达新节点 cur - s
每减一次平方数,相当于走一条边。问题就变成:

n0 的最短路径长度是多少?

BFS 天然求无权图最短路,层数即答案。

BFS 时序示意

Level(step) Visited Queue Level(step) Visited Queue 初始:Q=[n], step=0 alt [生成0] [未生成0] 取当前层全部节点 对每个cur,尝试cur-j*j 新状态且未访问则入队 返回step+1 step++ 进入下一层

Java 代码

import java.util.*;

class Solution {
    public int numSquares(int n) {
        Queue<Integer> queue = new LinkedList<>();
        boolean[] visited = new boolean[n + 1];

        queue.offer(n);
        visited[n] = true;
        int step = 0;

        while (!queue.isEmpty()) {
            int size = queue.size();
            step++;
            for (int k = 0; k < size; k++) {
                int cur = queue.poll();
                for (int j = 1; j * j <= cur; j++) {
                    int next = cur - j * j;
                    if (next == 0) return step;
                    if (!visited[next]) {
                        visited[next] = true;
                        queue.offer(next);
                    }
                }
            }
        }
        return step;
    }
}

复杂度分析

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

解法三:数学定理(性能很强)

原理

利用两个经典定理:

  1. 拉格朗日四平方定理:任意正整数都可表示为最多 4 个平方数之和
    → 答案只可能是 1,2,3,4

  2. 勒让德三平方定理推论:若 n = 4^a(8b+7),则 n 不能表示为 3 个平方数之和
    → 这种情况答案一定是 4

判定流程:

  1. n 本身是完全平方数,答案 1
  2. n 中因子 4 全部除掉(while n % 4 == 0
  3. n % 8 == 7,答案 4
  4. 枚举 a^2,判断 n-a^2 是否平方数,若是则答案 2
  5. 否则答案 3

判定流程图

输入 n

n 是否完全平方数?

返回 1

不断除以4: while n%4==0

n%8==7?

返回 4

枚举 a:1..sqrt(n)

n-a*a 是否完全平方数?

返回 2

继续枚举?

返回 3

Java 代码

class Solution {
    public int numSquares(int n) {
        if (isSquare(n)) return 1;

        // 去掉 4 的因子
        while (n % 4 == 0) {
            n /= 4;
        }

        // n = 8b + 7 的形式 => 4
        if (n % 8 == 7) return 4;

        // 检查是否可由两个平方数组成
        for (int a = 1; a * a <= n; a++) {
            int b = n - a * a;
            if (isSquare(b)) return 2;
        }

        // 不是1、2、4,则是3
        return 3;
    }

    private boolean isSquare(int x) {
        int r = (int) Math.sqrt(x);
        return r * r == x;
    }
}

复杂度分析

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

不同解法的实现复杂度与性能对比

解法核心思想时间复杂度空间复杂度实现复杂度性能表现适用场景
动态规划dp[i] 最优子结构O(n*sqrt(n))O(n)中等稳定、通用面试高频,易讲清楚
BFS无权图最短路(层数)最坏 O(n*sqrt(n))O(n)中等偏上可能较快,依赖剪枝与数据分布想用图论视角时
数学定理四平方/三平方定理判定O(sqrt(n))O(1)较高(需要定理)通常最快追求极致性能、理解数学结论后使用

总结

如果你想要最稳妥的解法:用 动态规划
如果你想要最优复杂度:用 数学定理法
如果你想训练图论建模能力:可以写 BFS

在 LeetCode 279 中,三种方法都很经典,建议至少掌握 DP + 数学法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TracyCoder123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值