完全平方数:当数论遇上动态规划
还记得小学时学过的平方数吗?1, 4, 9, 16, 25...这些数有着独特的魅力。今天我们要聊的 LeetCode 279 题就和这些数字有关,它将带我们领略动态规划与数论的完美结合。
🎯 问题本质
给你一个正整数 n,要求找到最少需要多少个完全平方数相加得到 n。比如:
输入:n = 12
输出:3
解释:12 = 4 + 4 + 4输入:n = 13
输出:2
解释:13 = 4 + 9乍看这道题,你可能会想:"要不要把所有可能的组合都试一遍?"但等等,让我们用动态规划的思维来思考这个问题。
💡 思维转变:从暴力到动态规划
想象你正在玩一个数字游戏。要拼出数字n,你每次可以选择一个完全平方数。这不就像是用最少的硬币凑出一定金额吗?这种联想让我们找到了突破口。
🔍 动态规划的设计过程
让我们一步步构建解决方案:
1. 定义状态
dp[i] 表示组成数字 i 需要的最少完全平方数个数。这个定义直接对应我们要解决的问题。
2. 寻找状态转移方程
假设我们现在要求 dp[n],我们可以:
- 先选一个完全平方数 j*j
- 然后问题就变成了求 dp[n - j*j]
- 遍历所有可能的 j,取最小值
所以状态转移方程就呼之欲出了: dp[i] = min(dp[i], dp[i - j*j] + 1),其中 j*j ≤ i
class Solution {
public int numSquares(int n) {
// dp[i] 表示组成i需要的最少完全平方数个数
int[] dp = new int[n + 1];
// 初始化:假设最坏情况下都由1组成
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
// 计算所有小于等于n的完全平方数
int maxSquareRoot = (int)Math.sqrt(n);
int[] squares = new int[maxSquareRoot + 1];
for (int i = 1; i <= maxSquareRoot; i++) {
squares[i] = i * i;
}
// 动态规划过程
for (int i = 1; i <= n; i++) {
// 尝试每个小于i的完全平方数
for (int j = 1; squares[j] <= i; j++) {
dp[i] = Math.min(dp[i], dp[i - squares[j]] + 1);
}
}
return dp[n];
}
// 数学方法:四平方和定理
public int numSquaresMath(int n) {
// 判断是否为完全平方数
if (isSquare(n)) return 1;
// 判断是否可以表示为两个完全平方数之和
for (int i = 1; i * i <= n; i++) {
if (isSquare(n - i * i)) return 2;
}
// 判断是否是4的倍数乘以8加7
while (n % 4 == 0) n /= 4;
if (n % 8 == 7) return 4;
// 其他情况下一定是3
return 3;
}
private boolean isSquare(int n) {
int sqrt = (int)Math.sqrt(n);
return sqrt * sqrt == n;
}
}🎯 代码的精髓
我们的解法提供了两种思路:动态规划和数学方法。让我们深入理解动态规划解法的每个部分:
预处理完全平方数:
- 提前计算所有可能用到的完全平方数
- 避免重复计算,提高效率
状态转移的实现:
- 对每个数i,尝试减去每个小于它的完全平方数
- 取所有可能情况的最小值
- 这个过程直观地体现了"选择"的概念
初始化的考虑:
- dp[0] = 0 作为基础case
- 其他位置初始化为最大值,方便取最小值
💡 优化的艺术:数学方法
除了动态规划,这道题还有一个令人惊叹的数学解法 —— 四平方和定理:任何自然数都可以表示为最多四个完全平方数的和。
更进一步,我们可以证明:
- 当n本身是完全平方数时,答案是1
- 当n可以表示为两个完全平方数之和时,答案是2
- 当n = 4^k * (8m + 7)时,答案是4
- 其他情况下,答案是3
🤔 思考题
如果问题变成:要求所有加数都必须是不同的完全平方数,解法会有什么变化?提示:状态的定义可能需要加入"已使用的最大完全平方数"这个维度。
📝 面试技巧
在面试中遇到这道题,建议这样展示你的思路:
先说明动态规划的思路:
- 定义状态表示的含义
- 推导状态转移方程
- 解释为什么这个方程是正确的
然后可以提到优化方向:
- 预处理完全平方数
- 提到数学方法的存在(加分项!)
记住,面试官更关心你的思维过程,而不仅仅是最终的解法。通过清晰地表达你如何一步步构建解决方案,你能展示出自己的问题解决能力。
作者:忍者算法
公众号:忍者算法
有人说算法是数学的艺术,这道题完美地诠释了这一点。它告诉我们,有时候一个问题可以有多种解法,而找到这些解法的过程,正是我们提升算法思维的过程。
#算法面试 #LeetCode #动态规划 #数学 #面试高频题