完全平方数
题目要求:
给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是
这是一道非常经典的算法题(对应 LeetCode 279. 完全平方数)。解决这道题最常见且最易于理解的方法是使用动态规划(Dynamic Programming)。
方法一:动态规划(首选)
思路:
我们可以定义一个数组dp,其中dp[i]表示和为i的完全平放数的最少数量
对于任意一个数字i,它可以由一个较小的数字i-jj加上一个完全平方数j j组成。
因此,状态转移方程为:dp[i] = Min(dp[i],dp[i-j* j]+1),其中j* j <= i。
Java代码实现如下:
class Solution {
public int numSquares(int n) {
// dp[i] 表示数字 i 最少可以由几个完全平方数相加构成
int[] dp = new int[n + 1];
// 初始状态,0 由 0 个完全平方数组成
dp[0] = 0;
// 遍历 1 到 n,计算每个数的 dp 值
for (int i = 1; i <= n; i++) {
// 最坏的情况就是每次都由 1 的平方组成,即 i 个 1
dp[i] = i;
// 尝试减去所有的完全平方数 j*j,找到最小值
for (int j = 1; j * j <= i; j++) {
dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
}
}
return dp[n];
}
}
复杂度分析:
- 时间复杂度: o(n*根号n)。外层循环执行n次,内层循环执行根号n次
- 空间复杂度:O(n)。需要长度为n+1的数组dp来保存中间状态
方法二:暴力搜索
暴力搜索通常是我们思考算法题的起点,对于这道题,最直接的暴力解法就是递归(回溯/深度优先1搜索DFS)
对于当前的数字n:
我们尝试减去所有小于等于n的完全平方数()
每减去一个完全平方数i^2, 剩下了 n-i^2。
我们递归地去求n-i^2需要地最少完全平方数数量
将所有尝试地结果进行比较,取最小值再加1(加上刚才减去的那个完全平方数),就是答案。
Java代码实现如下:
class Solution{
public int numSquares(int n){
//递归终止条件:如果n是0,说明刚好被完全平方数扣完了,需要0个数
if(n==0){
return 0;
}
//初始化为一个比较大的数。因为最坏情况是由n个1组成,所以n就是理论最大值
int minCount = n;
//尝试当前可以选择的所有完全平方数(1,4,9,16)
for(int i = 1;i*i<=n;i++){
//递归调用:当前消耗了1个完全平方数(i*i),剩下的数字n-i*i仍给下一层递归
int count = numSquares(n-i*i) +1;
minCount = Math.min(minCount,count);
}
return minCount;
}
暴力解法为什么在实际提交中会失败(超时 TLE)?
暴力解法的逻辑是绝对正确且严密的,但它有一个致命的缺陷:包含海量的重复计算(重叠子问题)。
我们以 numSquares(12) 为例,看看递归的展开过程:
-
当尝试减去1时,进入递归求numSquares(11)。
- 在算numSquares(11) 时,尝试减去 4,会去求 numSquares(7)。
-
当尝试减去 4 时,进入递归求 numSquares(8)。
- 在算 numSquares(8) 时,尝试减去 1,也会去求 numSquares(7)。
你会发现,数字 7 被计算了两次!随着 n 的增大,这种重复计算会呈指数级爆炸式增长。
- 在算 numSquares(8) 时,尝试减去 1,也会去求 numSquares(7)。
-
时间复杂度:极高
-
空间复杂度:O(n),主要是递归调用栈的最大深度(最坏情况下每次减1,递归n层)
从暴力到动态规划(顿悟时刻)
看到这里,你可能发现了:动态规划其实就是带了备忘录的暴力解法!
既然暴力解法慢是因为“重复计算了相同的 numSquares(x)”,那我们是不是可以在第一次算出 numSquares(7) 时,就把它的答案记在一个小本本(数组或哈希表)上?下次再遇到 7 时,直接翻本子查答案,不往下递归了。
- 暴力递归 + 记忆化(备忘录) = 自顶向下的动态规划
- 直接从小到大填表 = 自底向上的动态规划
这就是算法优化的美妙之处:暴力解法指明了问题的拆解方向,而动态规划修补了暴力解法效率低下的漏洞。
浙公网安备 33010602011771号