开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 13 天,点击查看活动详情
给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
示例 1:
输入:n = 12 输出:3 解释:12 = 4 + 4 + 4
示例 2:
输入:n = 13 输出:2 解释:13 = 4 + 9
提示:
1 <= n <= 104
解决思路
贪心枚举
递归解决方法为我们理解问题提供了简洁直观的方法。我们仍然可以用递归解决这个问题。为了改进上述暴力枚举解决方案,我们可以在递归中加入贪心。我们可以将枚举重新格式化如下:
从一个数字到多个数字的组合开始,一旦我们找到一个可以组合成给定数字 n 的组合。
那么我们可以说我们找到了最小的组合,因为我们贪心的从小到大的枚举组合。
为了更好的解释,我们首先定义一个名为 is_divided_by(n, count) 的函数,该函数返回一个布尔值,表示数字 n是否可以被一个数字 count 组合,而不是像前面函数 numSquares(n) 返回组合的确切大小。
与递归函数 numSquare(n) 不同,is_divided_by(n, count) 的递归过程可以归结为底部情况(即 count==1)更快。
下面是一个关于函数 is_divided_by(n, count) 的例子,它对 输入 n=5 和 count=2 进行了分解。
通过这种重新构造的技巧,我们可以显著降低堆栈溢出的风险。
算法:
- 首先,我们准备一个小于给定数字
n的完全平方数列表(称为square_nums)。 - 在主循环中,将组合的大小(称为
count)从1迭代到n,我们检查数字n是否可以除以组合的和,即is_divided_by(n, count)。 - 函数
is_divided_by(n, count)可以用递归的形式实现,汝上面所说。 - 在最下面的例子中,我们有
count==1,我们只需检查数字n是否本身是一个完全平方数。可以在square_nums中检查,即
- 。如果
square_nums使用的是集合数据结构,我们可以获得比n == int(sqrt(n)) ^ 2更快的运行时间。
关于算法的正确性,通常情况下,我们可以用反证法来证明贪心算法。这也不例外。假设我们发现 count=m 可以除以 n,并且假设在以后的迭代中存在另一个 count=p 也可以除以 n,并且这个数的组合小于找到的数,即 p<m。如果给定迭代的顺序,count = p 会在 count=m 之前被发现,因此,该算法总是能够找到组合的最小大小。
下面是一些示例实现。Python 解决方案需要大约 70ms,这比当时大约 90% 的提交要快。
private:
static bool unfilled;
static vector<int> dp;
static void fill() {
dp[1] = 1;
for (int i = 2; i < 10001; ++i) {
int min_ = INT_MAX;
for (int j = 1; j * j <= i; ++j) {
min_ = min(min_, dp[i - j*j]);
}
dp[i] = 1 + min_;
}
unfilled = false;
}
public:
int numSquares(int n) {
if (unfilled)
fill();
return dp[n];
}
};
bool Solution::unfilled = true;
vector<int> Solution::dp = vector<int> (10001, 0);